From 6fcdcaaf24c02a6c9f5278cf791b903b010834a9 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 2 Sep 2024 11:05:27 +0200 Subject: [PATCH 01/99] * redo changes of MIHO/Post_SpecConf_AsciiDoc --- src/AasxCsharpLibrary/AdminShellUtil.cs | 41 +++++++++++++++- .../Smt/AnyUiDialogueSmtExport.cs | 19 ++++++-- src/AasxPluginExportTable/Smt/ExportSmt.cs | 47 +++++++++++++++++-- .../Smt/ExportSmtRecord.cs | 3 ++ 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index d4d21820c..906e60307 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -1090,11 +1090,12 @@ public static string GetTemporaryDirectory() // see: https://stackoverflow.com/questions/6386113/using-system-io-packaging-to-generate-a-zip-file public static void AddFileToZip( - string zipFilename, string fileToAdd, + string zipFilename, + string fileToAdd, CompressionOption compression = CompressionOption.Normal, FileMode fileMode = FileMode.OpenOrCreate) { - using (Package zip = System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate)) + using (Package zip = System.IO.Packaging.Package.Open(zipFilename, fileMode)) { string destFilename = ".\\" + Path.GetFileName(fileToAdd); Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative)); @@ -1113,6 +1114,42 @@ public static void AddFileToZip( } } + public static void RecursiveAddDirToZip( + Package zip, + string localPath, + string zipPath = "", + CompressionOption compression = CompressionOption.Normal) + { + // enumerate only on this level + foreach (var infn in Directory.EnumerateDirectories(localPath, "*")) + { + // recurse + RecursiveAddDirToZip( + zip, + localPath: infn, + zipPath: Path.Combine(zipPath, Path.GetFileName(infn)), + compression: compression); + } + + foreach (var infn in Directory.EnumerateFiles(localPath, "*")) + { + string destFilename = ".\\" + Path.Combine(zipPath, Path.GetFileName(infn)); + Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative)); + if (zip.PartExists(uri)) + { + zip.DeletePart(uri); + } + PackagePart part = zip.CreatePart(uri, "", compression); + using (FileStream fileStream = new FileStream(infn, FileMode.Open, FileAccess.Read)) + { + using (Stream dest = part.GetStream()) + { + fileStream.CopyTo(dest); + } + } + } + } + // // some URL enabled path handling // diff --git a/src/AasxPluginExportTable/Smt/AnyUiDialogueSmtExport.cs b/src/AasxPluginExportTable/Smt/AnyUiDialogueSmtExport.cs index f57388cf3..11c67ee8c 100644 --- a/src/AasxPluginExportTable/Smt/AnyUiDialogueSmtExport.cs +++ b/src/AasxPluginExportTable/Smt/AnyUiDialogueSmtExport.cs @@ -80,7 +80,7 @@ public static async Task ExportSmtDialogBased( var panel = new AnyUiStackPanel(); var helper = new AnyUiSmallWidgetToolkit(); - var g = helper.AddSmallGrid(5, 2, new[] { "220:", "*" }, + var g = helper.AddSmallGrid(6, 2, new[] { "220:", "*" }, padding: new AnyUiThickness(0, 5, 0, 5)); panel.Add(g); @@ -153,13 +153,26 @@ public static async Task ExportSmtDialogBased( colSpan: 2), (b) => { record.ExportHtml = b; }); - // Row 4 : Export PDF - helper.AddSmallLabelTo(g, 4, 0, content: "Export PDF:", + // Row 4 : Export Antora + helper.AddSmallLabelTo(g, 4, 0, content: "Antora style:", verticalAlignment: AnyUiVerticalAlignment.Center, verticalContentAlignment: AnyUiVerticalAlignment.Center); AnyUiUIElement.SetBoolFromControl( helper.Set( helper.AddSmallCheckBoxTo(g, 4, 1, + content: "(dedicated sub-folders for images and diagrams)", + isChecked: record.AntoraStyle, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (b) => { record.AntoraStyle = b; }); + + // Row 4 : Export PDF + helper.AddSmallLabelTo(g, 5, 0, content: "Export PDF:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, 5, 1, content: "(export command given by options will be executed)", isChecked: record.ExportPdf, verticalContentAlignment: AnyUiVerticalAlignment.Center), diff --git a/src/AasxPluginExportTable/Smt/ExportSmt.cs b/src/AasxPluginExportTable/Smt/ExportSmt.cs index a46a15afc..419e010ec 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmt.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmt.cs @@ -31,6 +31,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxPluginExportTable.Table; using System.Runtime.Intrinsics.X86; using AnyUi; +using System.IO.Packaging; namespace AasxPluginExportTable.Smt { @@ -50,6 +51,10 @@ public class ExportSmt protected StringBuilder _adoc = new StringBuilder(); protected bool _singleFile = true; + protected string _locationPages = ""; + protected string _locationImages = ""; + protected string _locationDiagrams = ""; + protected void ProcessTextBlob(string header, Aas.IBlob blob) { // any content @@ -171,7 +176,7 @@ protected void ProcessImageLink(Aas.ISubmodelElement sme) fn = args.fileName; // save absolute - var absFn = Path.Combine(_tempDir, fn); + var absFn = Path.Combine(_locationImages, fn); File.WriteAllBytes(absFn, data); _log?.Info("Image data with {0} bytes writen to {1}.", data.Length, absFn); @@ -213,7 +218,7 @@ protected void ProcessUml(Aas.IReferenceElement refel) if (refel.IdShort.HasContent()) pumlName = AdminShellUtil.FilterFriendlyName(refel.IdShort); var pumlFn = pumlName + ".puml"; - var absPumlFn = Path.Combine(_tempDir, pumlFn); + var absPumlFn = Path.Combine(_locationDiagrams, pumlFn); // make options var umlOptions = new ExportUmlRecord(); @@ -349,6 +354,33 @@ public void ExportSmtToFile( _tempDir = AdminShellUtil.GetTemporaryDirectory(); log?.Info("ExportSmt: using temp directory {0} ..", _tempDir); + _locationPages = _tempDir; + _locationImages = _tempDir; + _locationDiagrams = _tempDir; + + // sub-folders? + if (optionsSmt.AntoraStyle) + { + try + { + _locationPages = Path.Combine(_tempDir, "pages"); + _locationImages = Path.Combine(_tempDir, "images"); + _locationDiagrams = Path.Combine(Path.Combine(_tempDir, "partials"), "diagrams"); + + Directory.CreateDirectory(_locationPages); + Directory.CreateDirectory(_locationImages); + Directory.CreateDirectory(Path.Combine(_tempDir, "partials")); + Directory.CreateDirectory(_locationDiagrams); + + _log?.Info(StoredPrint.Color.Black, + "Created dedicated sub-folders for pages, images, partials/diagrams."); + } + catch (Exception ex) + { + _log?.Error(ex, "Creating sub-folders within " + _tempDir); + } + } + // predefined semantic ids var defs = AasxPredefinedConcepts.AsciiDoc.Static; var mm = MatchMode.Relaxed; @@ -402,7 +434,7 @@ public void ExportSmtToFile( ? AdminShellUtil.FilterFriendlyName(_srcSm.IdShort) : "output"; var adocFn = title + ".adoc"; - var absAdocFn = Path.Combine(_tempDir, adocFn); + var absAdocFn = Path.Combine(_locationPages, adocFn); // write it File.WriteAllText(absAdocFn, adocText); @@ -439,6 +471,7 @@ public void ExportSmtToFile( else { // create zip package +#if __old_ var first = true; foreach (var infn in Directory.EnumerateFiles(_tempDir, "*")) { @@ -447,6 +480,14 @@ public void ExportSmtToFile( fileMode: first ? FileMode.Create : FileMode.OpenOrCreate); first = false; } +#else + using (Package zip = System.IO.Packaging.Package.Open(fn, FileMode.Create)) + { + AdminShellUtil.RecursiveAddDirToZip( + zip, + _tempDir); + } +#endif log?.Info("ExportSmt: packed all files to {0}", fn); } diff --git a/src/AasxPluginExportTable/Smt/ExportSmtRecord.cs b/src/AasxPluginExportTable/Smt/ExportSmtRecord.cs index e17c8c823..0d96a5f4d 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmtRecord.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmtRecord.cs @@ -34,6 +34,9 @@ public class ExportSmtRecord [AasxMenuArgument(help: "If true, will include table data in main AsciiDoc file.")] public bool IncludeTables = true; + [AasxMenuArgument(help: "If true, will do dedicated sub-folders for images and diagrams.")] + public bool AntoraStyle = false; + [AasxMenuArgument(help: "If true, will execute external program to produce HTML from AsciiDic.")] public bool ExportHtml = false; From 3c0aec1607ca2fb9d9145046e54714c3e7cd1816 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Tue, 3 Sep 2024 14:36:20 +0200 Subject: [PATCH 02/99] * fix w.r.t. missing CoundtryFlags nuget package. * backported from Blazor --- .../AasxIntegrationBaseWpf.csproj | 6 + .../CountryFlags/CountryFlagWpf.cs | 362 ++++++++++++++++++ .../Resources/country_flags.png | Bin 0 -> 217393 bytes src/AasxPackageExplorer/MainWindow.xaml.cs | 2 + src/AasxWpfControlLibrary/AnyUiWpf.cs | 18 +- 5 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 src/AasxIntegrationBaseWpf/CountryFlags/CountryFlagWpf.cs create mode 100644 src/AasxIntegrationBaseWpf/Resources/country_flags.png diff --git a/src/AasxIntegrationBaseWpf/AasxIntegrationBaseWpf.csproj b/src/AasxIntegrationBaseWpf/AasxIntegrationBaseWpf.csproj index 5318a8d60..7922a9f9c 100644 --- a/src/AasxIntegrationBaseWpf/AasxIntegrationBaseWpf.csproj +++ b/src/AasxIntegrationBaseWpf/AasxIntegrationBaseWpf.csproj @@ -6,11 +6,17 @@ true false + + + + + True + diff --git a/src/AasxIntegrationBaseWpf/CountryFlags/CountryFlagWpf.cs b/src/AasxIntegrationBaseWpf/CountryFlags/CountryFlagWpf.cs new file mode 100644 index 000000000..8ebb317c2 --- /dev/null +++ b/src/AasxIntegrationBaseWpf/CountryFlags/CountryFlagWpf.cs @@ -0,0 +1,362 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + + +/* +* Flag images from: https://github.com/MikeCodesDotNET/BlazorFlags +* License: MIT +* No sourcecode taken over; pixel offset reformatted. +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Imaging; +using AasxIntegrationBase; +using AdminShellNS; +using AnyUi; +using Newtonsoft.Json; + +// ReSharper disable UnusedType.Global + +namespace AasxIntegrationBaseWpf +{ + public static class CountryFlagWpf + { + public static string ResourcePathFlags = ""; + + public static BitmapImage FlagsImage = null; + + public class FlagInfo + { + /// + /// Code according ISO 3166-2 + /// + public string Code2 = ""; + + /// + /// Pixel offset + /// + public int Offset = 0; + } + + public static Dictionary Code2ToFlagInfo = new Dictionary(); + + static CountryFlagWpf() + { + InitFI(); + LoadImage(); + } + + public static void LoadImage() + { + try + { + FlagsImage = new BitmapImage( + new Uri("pack://application:,,,/AasxIntegrationBaseWpf;component/Resources/country_flags.png", UriKind.RelativeOrAbsolute)); + + if (FlagsImage.PixelWidth < 1) + FlagsImage = null; + } + catch (Exception ex) + { + FlagsImage = null; + LogInternally.That.CompletelyIgnoredError(ex); + } + } + + public static void AddFI(string code3, string code2, int ofs) + { + code2 = code2?.ToLower().Trim(); + Code2ToFlagInfo.Add(code2, new FlagInfo() { Code2 = code2, Offset = ofs }); + } + + public static void InitFI() + { + Code2ToFlagInfo.Clear(); + AddFI("", "ad", ofs: 42); + AddFI("are", "ae", ofs: 82); + AddFI("afg", "af", ofs: 123); + AddFI("atg", "ag", ofs: 164); + AddFI("aia", "ai", ofs: 205); + AddFI("alb", "al", ofs: 246); + AddFI("arm", "am", ofs: 287); + AddFI("ant", "an", ofs: 328); + AddFI("ago", "ao", ofs: 369); + AddFI("ata", "aq", ofs: 410); + AddFI("arg", "ar", ofs: 451); + AddFI("asm", "as", ofs: 492); + AddFI("aut", "at", ofs: 533); + AddFI("aus", "au", ofs: 574); + AddFI("abw", "aw", ofs: 615); + AddFI("ala", "ax", ofs: 657); + AddFI("aze", "az", ofs: 698); + AddFI("bih", "ba", ofs: 738); + AddFI("brb", "bb", ofs: 779); + AddFI("bgd", "bd", ofs: 820); + AddFI("bel", "be", ofs: 861); + AddFI("bfa", "bf", ofs: 902); + AddFI("bgr", "bg", ofs: 943); + AddFI("bhr", "bh", ofs: 984); + AddFI("bdi", "bi", ofs: 1025); + AddFI("ben", "bj", ofs: 1067); + AddFI("blm", "bl", ofs: 1107); + AddFI("bmu", "bm", ofs: 1148); + AddFI("brn", "bn", ofs: 1189); + AddFI("bol", "bo", ofs: 1230); + AddFI("bes", "bq", ofs: 1272); + AddFI("bra", "br", ofs: 1312); + AddFI("bhs", "bs", ofs: 1353); + AddFI("btn", "bt", ofs: 1394); + AddFI("bvt", "bv", ofs: 1435); + AddFI("bwa", "bw", ofs: 1477); + AddFI("blr", "by", ofs: 1517); + AddFI("blz", "bz", ofs: 1558); + AddFI("can", "ca", ofs: 1599); + AddFI("cck", "cc", ofs: 1640); + AddFI("cod", "cd", ofs: 1681); + AddFI("caf", "cf", ofs: 1722); + AddFI("cog", "cg", ofs: 1763); + AddFI("che", "ch", ofs: 1804); + AddFI("civ", "ci", ofs: 1845); + AddFI("cok", "ck", ofs: 1886); + AddFI("chl", "cl", ofs: 1927); + AddFI("cmr", "cm", ofs: 1968); + AddFI("chn", "cn", ofs: 2009); + AddFI("col", "co", ofs: 2050); + AddFI("cri", "cr", ofs: 2091); + AddFI("cub", "cu", ofs: 2132); + AddFI("cpv", "cv", ofs: 2173); + AddFI("cuw", "cw", ofs: 2214); + AddFI("cxr", "cx", ofs: 2255); + AddFI("cyp", "cy", ofs: 2296); + AddFI("cze", "cz", ofs: 2337); + AddFI("deu", "de", ofs: 2377); + AddFI("dji", "dj", ofs: 2419); + AddFI("dnk", "dk", ofs: 2460); + AddFI("dma", "dm", ofs: 2501); + AddFI("dom", "do", ofs: 2542); + AddFI("dza", "dz", ofs: 2583); + AddFI("ecu", "ec", ofs: 2624); + AddFI("est", "ee", ofs: 2665); + AddFI("egy", "eg", ofs: 2706); + AddFI("esh", "eh", ofs: 2747); + AddFI("eri", "er", ofs: 2787); + AddFI("", "es-ca", ofs: 2829); + AddFI("esp", "es", ofs: 2870); + AddFI("eth", "et", ofs: 2911); + AddFI("", "eu", ofs: 2953); + AddFI("fin", "fi", ofs: 2993); + AddFI("fji", "fj", ofs: 3034); + AddFI("flk", "fk", ofs: 3075); + AddFI("fsm", "fm", ofs: 3116); + AddFI("fro", "fo", ofs: 3157); + AddFI("fra", "fr", ofs: 3198); + AddFI("gab", "ga", ofs: 3239); + AddFI("", "gb-eng", ofs: 3280); + AddFI("", "gb-nir", ofs: 3321); + AddFI("", "gb-sct", ofs: 3362); + AddFI("", "gb-wls", ofs: 3403); + AddFI("gbr", "gb", ofs: 3444); + AddFI("grd", "gd", ofs: 3485); + AddFI("geo", "ge", ofs: 3526); + AddFI("guf", "gf", ofs: 3567); + AddFI("ggy", "gg", ofs: 3608); + AddFI("gha", "gh", ofs: 3649); + AddFI("gib", "gi", ofs: 3690); + AddFI("grl", "gl", ofs: 3731); + AddFI("gmb", "gm", ofs: 3771); + AddFI("gin", "gn", ofs: 3813); + AddFI("glp", "gp", ofs: 3854); + AddFI("gnq", "gq", ofs: 3895); + AddFI("grc", "gr", ofs: 3936); + AddFI("sgs", "gs", ofs: 3977); + AddFI("gtm", "gt", ofs: 4018); + AddFI("gum", "gu", ofs: 4059); + AddFI("gnb", "gw", ofs: 4100); + AddFI("guy", "gy", ofs: 4141); + AddFI("hkg", "hk", ofs: 4182); + AddFI("hmd", "hm", ofs: 4223); + AddFI("hnd", "hn", ofs: 4264); + AddFI("hrv", "hr", ofs: 4305); + AddFI("hti", "ht", ofs: 4347); + AddFI("hun", "hu", ofs: 4387); + AddFI("idn", "id", ofs: 4428); + AddFI("irl", "ie", ofs: 4468); + AddFI("isr", "il", ofs: 4510); + AddFI("imn", "im", ofs: 4551); + AddFI("ind", "in", ofs: 4593); + AddFI("iot", "io", ofs: 4633); + AddFI("irq", "iq", ofs: 4674); + AddFI("irn", "ir", ofs: 4715); + AddFI("isl", "is", ofs: 4756); + AddFI("ita", "it", ofs: 4797); + AddFI("jey", "je", ofs: 4838); + AddFI("jam", "jm", ofs: 4879); + AddFI("jor", "jo", ofs: 4920); + AddFI("jpn", "jp", ofs: 4961); + AddFI("ken", "ke", ofs: 5002); + AddFI("kgz", "kg", ofs: 5043); + AddFI("khm", "kh", ofs: 5084); + AddFI("kir", "ki", ofs: 5125); + AddFI("com", "km", ofs: 5166); + AddFI("kna", "kn", ofs: 5207); + AddFI("prk", "kp", ofs: 5248); + AddFI("kor", "kr", ofs: 5289); + AddFI("kwt", "kw", ofs: 5330); + AddFI("cym", "ky", ofs: 5371); + AddFI("kaz", "kz", ofs: 5412); + AddFI("lao", "la", ofs: 5453); + AddFI("lbn", "lb", ofs: 5494); + AddFI("lca", "lc", ofs: 5535); + AddFI("lie", "li", ofs: 5576); + AddFI("lka", "lk", ofs: 5617); + AddFI("lbr", "lr", ofs: 5658); + AddFI("lso", "ls", ofs: 5698); + AddFI("ltu", "lt", ofs: 5740); + AddFI("lux", "lu", ofs: 5781); + AddFI("lva", "lv", ofs: 5822); + AddFI("lby", "ly", ofs: 5862); + AddFI("mar", "ma", ofs: 5904); + AddFI("mco", "mc", ofs: 5945); + AddFI("mda", "md", ofs: 5986); + AddFI("mne", "me", ofs: 6028); + AddFI("maf", "mf", ofs: 6068); + AddFI("mdg", "mg", ofs: 6109); + AddFI("mhl", "mh", ofs: 6150); + AddFI("mkd", "mk", ofs: 6191); + AddFI("mli", "ml", ofs: 6233); + AddFI("mmr", "mm", ofs: 6273); + AddFI("mng", "mn", ofs: 6314); + AddFI("mac", "mo", ofs: 6355); + AddFI("mnp", "mp", ofs: 6397); + AddFI("mtq", "mq", ofs: 6437); + AddFI("mrt", "mr", ofs: 6478); + AddFI("msr", "ms", ofs: 6519); + AddFI("mlt", "mt", ofs: 6560); + AddFI("mus", "mu", ofs: 6601); + AddFI("mdv", "mv", ofs: 6642); + AddFI("mwi", "mw", ofs: 6682); + AddFI("mex", "mx", ofs: 6724); + AddFI("mys", "my", ofs: 6765); + AddFI("moz", "mz", ofs: 6806); + AddFI("nam", "na", ofs: 6847); + AddFI("ncl", "nc", ofs: 6888); + AddFI("ner", "ne", ofs: 6929); + AddFI("nfk", "nf", ofs: 6970); + AddFI("nga", "ng", ofs: 7011); + AddFI("nic", "ni", ofs: 7052); + AddFI("nld", "nl", ofs: 7093); + AddFI("nor", "no", ofs: 7134); + AddFI("npl", "np", ofs: 7175); + AddFI("nru", "nr", ofs: 7217); + AddFI("niu", "nu", ofs: 7257); + AddFI("nzl", "nz", ofs: 7298); + AddFI("omn", "om", ofs: 7339); + AddFI("pan", "pa", ofs: 7380); + AddFI("per", "pe", ofs: 7421); + AddFI("pyf", "pf", ofs: 7463); + AddFI("png", "pg", ofs: 7503); + AddFI("phl", "ph", ofs: 7544); + AddFI("pak", "pk", ofs: 7585); + AddFI("pol", "pl", ofs: 7626); + AddFI("spm", "pm", ofs: 7667); + AddFI("pcn", "pn", ofs: 7708); + AddFI("pri", "pr", ofs: 7749); + AddFI("pse", "ps", ofs: 7790); + AddFI("prt", "pt", ofs: 7831); + AddFI("plw", "pw", ofs: 7873); + AddFI("pry", "py", ofs: 7913); + AddFI("qat", "qa", ofs: 7954); + AddFI("reu", "re", ofs: 7995); + AddFI("rou", "ro", ofs: 8036); + AddFI("srb", "rs", ofs: 8077); + AddFI("rus", "ru", ofs: 8117); + AddFI("rwa", "rw", ofs: 8159); + AddFI("sau", "sa", ofs: 8200); + AddFI("slb", "sb", ofs: 8241); + AddFI("syc", "sc", ofs: 8282); + AddFI("sdn", "sd", ofs: 8323); + AddFI("swe", "se", ofs: 8364); + AddFI("sgp", "sg", ofs: 8405); + AddFI("shn", "sh", ofs: 8446); + AddFI("svn", "si", ofs: 8487); + AddFI("sjm", "sj", ofs: 8528); + AddFI("svk", "sk", ofs: 8569); + AddFI("sle", "sl", ofs: 8610); + AddFI("smr", "sm", ofs: 8651); + AddFI("sen", "sn", ofs: 8693); + AddFI("som", "so", ofs: 8733); + AddFI("sur", "sr", ofs: 8774); + AddFI("ssd", "ss", ofs: 8815); + AddFI("stp", "st", ofs: 8856); + AddFI("slv", "sv", ofs: 8897); + AddFI("sxm", "sx", ofs: 8938); + AddFI("syr", "sy", ofs: 8979); + AddFI("swz", "sz", ofs: 9020); + AddFI("tca", "tc", ofs: 9061); + AddFI("tcd", "td", ofs: 9102); + AddFI("atf", "tf", ofs: 9142); + AddFI("tgo", "tg", ofs: 9184); + AddFI("tha", "th", ofs: 9225); + AddFI("tjk", "tj", ofs: 9266); + AddFI("tkl", "tk", ofs: 9307); + AddFI("tls", "tl", ofs: 9348); + AddFI("tkm", "tm", ofs: 9389); + AddFI("tun", "tn", ofs: 9430); + AddFI("ton", "to", ofs: 9472); + AddFI("tur", "tr", ofs: 9512); + AddFI("tto", "tt", ofs: 9552); + AddFI("tuv", "tv", ofs: 9594); + AddFI("twn", "tw", ofs: 9635); + AddFI("tza", "tz", ofs: 9676); + AddFI("ukr", "ua", ofs: 9717); + AddFI("uga", "ug", ofs: 9757); + AddFI("umi", "um", ofs: 9799); + AddFI("", "un", ofs: 9840); + AddFI("usa", "us", ofs: 9881); + AddFI("ury", "uy", ofs: 9922); + AddFI("uzb", "uz", ofs: 9963); + AddFI("vat", "va", ofs: 10004); + AddFI("vct", "vc", ofs: 10045); + AddFI("ven", "ve", ofs: 10086); + AddFI("vgb", "vg", ofs: 10127); + AddFI("vir", "vi", ofs: 10168); + AddFI("vnm", "vn", ofs: 10209); + AddFI("vut", "vu", ofs: 10250); + AddFI("wlf", "wf", ofs: 10291); + AddFI("wsm", "ws", ofs: 10331); + AddFI("", "xk", ofs: 10373); + AddFI("yem", "ye", ofs: 10414); + AddFI("myt", "yt", ofs: 10455); + AddFI("zaf", "za", ofs: 10496); + AddFI("zmb", "zm", ofs: 10538); + AddFI("zwe", "zw", ofs: 10578); + AddFI("sun", "su", ofs: 10619); + } + + public static CroppedBitmap GetCroppedFlag(string code2) + { + code2 = code2?.ToLower().Trim(); + if (FlagsImage == null || code2?.HasContent() != true || + !Code2ToFlagInfo.ContainsKey(code2)) + return null; + var fi = Code2ToFlagInfo[code2]; + + return new CroppedBitmap(FlagsImage, new Int32Rect(0, fi.Offset, FlagsImage.PixelWidth, 40)); + } + } +} diff --git a/src/AasxIntegrationBaseWpf/Resources/country_flags.png b/src/AasxIntegrationBaseWpf/Resources/country_flags.png new file mode 100644 index 0000000000000000000000000000000000000000..05dea4299791ee5d3584ca0d6fcc6a70be50a388 GIT binary patch literal 217393 zcma%ib9g09-)3w(CpIRwZQHhOI}_V>CY)$uO>En??alLk*!SDrKlWVLIn}4DyQ;gY z?jP>HJ3>KD93BP$0|W#FFDW6S^j)$50Z|o0L40ooaXTIX0Re|t3JWVp3JViDIN6(7 z+L!_XQ6~5%@JjRv;11|3HlskDZhc3G1l+chF@ zr-sl&7J%YS7gI1xWu%albivhsy1sd!8*Jdpdvm_8;EV$1OYlg=E|u*NRn%Ek)TU7Oj?p21~UX=YEaY<`q zFj+$UgzRo_x<%d7Q{q5y>E@UmVnA932e1yk@VR9VLT~#+)ZC|bD?ys^N!B(s9wSdT z!|UiYvzQ{btOG;j!mT9hj>szy9|b-!zn0QW`@@DTc5G(8argqw&a2)+aBzT$!6bKX z?riivZhrF4onJu;=Q<^qbUEdJPYZ;Rw7AH3`TcnjZukE#pdBPMoq>R0QUCb@17&7o zeeZ;Hk(3pMJb;9OhUA*ju^<2fA_S5Y5mfP5J1JM&X6pnYA`Jl$frqRef_%#*Dw>D=kcwShj0R$`hYgQ3B_y#)A-DvvQoz583UNjD{a#*tX75-xBJy1 z$oAK~cHFr~=p#!_|Jsqp;l*f0iNX8DL3Z01Zf*W)@fq3Io};3DjY&>!l0}q2BIMx7 z;;AGMmV>T98Qw^eD(8pwkB3mM$Elu+wKbYN@7Md53MoRnb>LYgiVFc5@DBgugGe-G~TR?5PN-$E2eaQ}PdT(lT$0p08S8yBh0 z_OSpOx>oBDDcV4|>)#<7$#y&)%8+LzS z)`1jCmG&UF)i0+^Wg&ZOTmcS7v~?0d!fd(%gQi*xo}+7#iD^2 z?|m#acSHX|nOb=X zIL>en)7yx*c5?cq5GW$jwZo?AhpwFnA%UrAq4Uun%WBPg!PhT^%W+6mhU9!No@%$G z)tw)ak=h3CA-lKf4xwQRO3Mk`l)Qz6yDqO#jP?U2jfHdEP)sS`b`-8j%g##x$SOv} zH^tM|5tBFl>%&93@Ym1;E02hjo zhdS-LT!*YIABai-CLW~tUj^|4eGr^$?8XrpB=cnj*ueh31MC}T`F8}_To8zqlNomh zU8O)O^cl*=8YUV_9(Q_?BmdUUcXPTPy_RO}ZUc8$UG0)!Wy66K$!Cq@@axIsb(E(6 z4ch;}ZbIptPdl9Ca%>R1>XC#>7u z(`qT)&&kVf?YlrbT4??0nJt2h<@LTcF)xCF2N&$|ye1Td#VkaC?W2F2{u6DxDv)Vs zi=-`DWQ7swdM8FY5?9-T^|r`=vwyffG9`|Y@6yNRp3#pZ+*s48fEkV;vv|d?Je=%rTyQQTBDj=k+U)aHl z9ybewB%JmG!oV&i+W1HiSKAF`o;qW@3lzw~ZZo1pnM*GV(N@F(m6lGMopyKP2rVw3JM_{&7gU;0KWcQ_hu^Po2%$C7 z=m-9|V`lD?nsm>H$jF6l$Np;cyJRgaT@TbT8ff58n!fit)~47-U2pWbBNJ*XMOm$+ z-wYn=u_Cb=#Ut&$=BL5qCV6|89UdEDGCfl_{xIdN-xn-F01?2!h$v@LYV$!~{VCyc zv08xP0@M=a#Q8kVuV{TQL0xy!YCF_rbDxu>c%nLNN*DC@D2R#@7#&T;g5Qgn$$%OJ zmwJ!?3=d!-a(wRaTi1SMCviuWx-~Jig_ifVR!A!*s`fT-qa7@Fi zghVv+Iyr53wLQ3eGnner65g-9a}y=z0g(NA;Rb_QMd-Bd;Np1Gzx73#`_ALDsMO)< z`CN5ytWBoS;(~m+%mLG-38v@ot0(jUSyn$Lyo1TVJ3;+dT_@UjOAnXZZJU-xWQhhr zkZgBsyyQ6Qy6018G32Rq_!x*%XFD)O&=^Io%h&SR-$rVctP4gJtdT39v( zzy1gn<^M)LNU;PjL>YOKWLJ5>Daq=mzh;%E zW#<;ZsVszRieW}qES?!&@K$%L1J^5_2%0OQt|u>(9E_Dm@$ILLC89ESp+_6xKXz3e z@}ZVjON*|V1YF=MJ2GVi9K1tH#WR+>ZL51lMpeMnP)evPnD<|AKOkW{Z% zvgajiQ*w(2AZ0AiiXz(3g_ChcHv6>|(%wf~Z&(?dg6*Ydx0sYsS)*dd=W^hjQTxg+ zl8p?t-BdGmV|JP(TWIMpR>SUj`1Ce)PBRIykEx==Z3Est0LN3ypbNu(^HJDUPG-MR zXq$3=bg{66d`Zn=&wwFM#jGaF@k1si3vnjaW>dqNZ4K-=tiSA1WD|8Alp+K_-Fb{y#Y?-Y)q;`g zh+k=KSE_=$VsKx2r)fvKlTWq58LZ@tdb}mDRmxWT44ss>ri24Y+Sc*z0fLn^ZXKO|}{UAJlg1nd83= z5$vV&wqGKUHQ+m~1&ImIuQpkDKk_s?HTZISo}3KRsm~2(kLuBjqEZm2EPnFp)K7@b zJl{NkDBBAFb&-fu=JK>IzNM`DvTyQwpH9@xTidO@^dTm9Kd4QNO_b#seZAkkRwpx` zwfaj;TSaJf~)Gm5pZdUgo~pjC3VRdnmhLpQ|h837Ami zT$hiI+djh_G|sp|CgZOyTep|*xq`A6ITuIMc)f^ zH~na=RM>by`Xj4t`LyB9;(uNvHW!b@cIbq_|IG77!5^(-x7P|;YjR`$go0}MYa#HS z3;v3SJC}XUii7JA;hr`tq5e=i2=ZZ9Lr!uJ*muj7Fye|w{A9Mt=E@)b&%|0wvAp)@ z`w4W|qpf;TFe!yd-Oe5roF+%wc-fcu`TR)Z+YV?WV*-svT~3Yqd)Z8Y%9wk4-U=;F z%V*P@1D(F~%Om-zX z+>v06$aC>bL>>^X9~S<_9QRf>F>rFr0*D6~%#7mQulr`N)OHH2FKs!Tudq{Twu*Q2hbQQ(AY?UXKLSk+jp1UmgF8P>Hh%7-H#*MXPEeceb`spY<%t6}#(oXKUuoSK zzb7(7O_hs{jrDZDImWTO0ywt7@%e#p+R=)4BQaDCAtj2eL zOzOw$lf+@H^;^N*ubV@R_`69OEgn|%lo1WhCUQ{fBxHR{Y|ksd2$7u@s!rMxPFXb4(+KHgmIp(o&ci%WUjE`LCnTSRi&Y6ZW*&(Id}()f?n@bmT? zU?a$@5u!SBX#eeeMReBU5JTu13PcI2R#>Y$ewo^Zt|URqTQFZ zk3dhnZ?yQ`A`RlD(}um`9Bvmwu6-Lc{tig)I%|jpVN+wIf6C7pn*b3veWR9X8lIg4 z=S9Lw57l}P`;MI%pX(xUUhtE?;miCdhCC~tq<)Y3xhpFD-iSVf6t(Lb)oVGNN9t-{ zU3vCftvPJIZ8Me|xCk2`Xplo&6Ws(IF+^Wk{mB&FgIa!OnBDhtH9w8OUPN0h=D9LO zhmbM+p#8d2_ExM%CdYvw(iK;#R9^GqsjOt@9NF35)|Gdia~`!hT0|urW6B#}5qK?6TW;cqR8D&=u6oUk zADjB@ZzcUGdE|H{DQ|R`K#<6Q`QQigRT%5OFCgK4{Cd@6elB3a2;$ZO z-OUeIFCr&+^D;KJpaLU3Du4r0im^=vEX`z63nq_!?|~1)7Q_qt%Y@<$!5c_;4&v20 zEl2tl^rEZ>u@hu9@niZ8{(4ma2Tr)FXK%!G$WM6=QO;i@HHyYtfB^WWub?l-;Q#=2 z1=7t6CTtd5PF8DxY0Bn0$KEQt=8A?j>-@C|RSk0X$fAJ}^h?$K`27lhV&_?K7 zk8_I(jjtKIuaBy@fsU&Q#FpQ$&TFkI@%no--Un=9A8Al{;g0AjzpSfVju3q=@BK{^ z;?QnsmzMCIJ@CkSG7Q>jq6LT3DstFjQpx|$Lq#(KpoaEPlO+WIBR@R;VxM-*ixcvH zAc=>4_ltc2l#bQQY36+;r!r{R=Qr!c>83vSU6A)(nEjauQ$zzX9_=GJDTgOzck|$p zp{4FYCH}^icjJtpFvNgC*sM z?9hAk?5uLkIVIEBbE=;@JNTvq0@0=o619RA(KJt87=97PA=TN5DVIs~hL5MQ;V_Sa z$j{(_2adsu4Q7nnIJA4z6l0--Y>5sLf*RjCU4zPphs{Wr9dWnE49G#oqWTKEYX-IU z+KB0fd%~T9-CrdVkCJ9c9umgB`L3&CVd+(4g84<*4tk|Ug-5|k&RWC=3hU9=hKWv# z*P2VnVO*k4$#$Y5OFE}y%G;WPMG;Shhvnrlx#W?BG>=wwSkaPcped4U3lEZw;Xb__ zyA+<|-sb}Patarm#*Zok6oL6UaW%;JM*y_Sw7o}D`+8LHJkqOjl?RaRIVF?I!OX;} z)Gk3iH{G5MW>&0rnl=02^sHFewoG+HdxAjtoceNxPVwN-9NP~}vlb$Em}~UcZJ2_RH^SKEgR)PUzRY{V8vFhA(bMJ-kmOU zr!V4$z-Eg1wVOS7t`2anPHUn{JxSPL32U%-p6on{?qk)_tQnez4!=ha_K&xCbUU;r z2s;C%;l&O)ZPu~T#&tu%eXy`2lv6Ehlu#AJpL`dfk0HZz=tsv^_8u61P-ki$92B3p zP^i#fD>xd8ntIoxJb->rvr5&NLwoBCZa+M@XXBE^KU)?_C+|Qh(kRxtbZsXtB9$t` zJ#KMX*ruj5ZX8%v4A&+WSiTK$pug#Au;-+tG%j5j#w~W~qC(}g@QCBjp6#k-9pbNQ*JR|5stTMWcL!!sUQZur+_^Z9?`CByjq`g0)KifIz?#CW@9u_g zAOnn5t<1zlotH6xUNi>vP?sg~uiGWE0Kx!pRqm}GJf!#8O)3;g4Pz+H=x7es3P>BS zUYqm{vrt=QgC>PZVo`)p<)5XlWi+U& zko4+hOlZH9R9mx%35l<(C&Vh$q}i(xatQNL!M-ho%Z$~_qFE(~Gn3WNioG8I98yVQ zkg2dR#x*nJpcR>NN)+Nq8HTKvQM0|3X$FARyLoU)9ZOKp`Y{&Fho94j-|f3!?}$tHK)UW$S_KCjhP{2yc*A$WO2 zhsAdP6y5&b#-RkD#!FxgOvYA?RU!w5D|Y;u<8H(Vk^{6{bL8~0w6Simg!CM z!vi)2sFC1IfUW6GlGc27AHh(6A`F3c26*FSNFa4;Lb9;mgWKwClTAx%MJ6LyncO!Q?LEcY(G~aT20NK}1Bh+Wg-wB*-h0`1( zTn33c>LF1sXd9Uf9-n50XR{4Nw+7H^lz${XVeY{%YGu&XR9A z7ut_dXE#g&-CwrYm){ahjY?o{CaKl?`M%#b4B@-|6>qTL!8@FxozUP8;m2;7pCs)C zxUH2=@C6^{_7~M-z-jeX-w$m_$k80m;U=zJhyhXd&L1KWu-hs<^%4~5`ULDps}2u@ z$FtLj>b=w6F0u{>zp(cDN`E(l0AI@axA)6ZP+nLi_kM-?;a&xM93iHbZ*#tb^RO zgyt%-E;E%rT>WjHd&CphZ6|hx#+ccilK&*dhhI)! z18Xi&kCRfPNAml2Zu1w)p_$H*!I7<7L2IYt)QbM@++V*ff|{Ij1(D{oh5R-^HKvz8 z>a$m8ql;fDKdLzkza0&SWyE>VVWdo1E=O{^oHDvN5lIX)9=`meo_6ZVKh?bhWyHbBo7CQeJvDaj;@q@j@#D;@MWlfOYfj>v$d& zt^4)&A^8;t)-uAht09!@Yrw9hh0hWsNPv^2cBGD zr*G!2z%Dv(Ti{c>s+=;DnYu07tO_gnu&_s;ikrD1mmH)tTz_?#f;IT*i0c(JjS7xk z$a$6gDV43%2!lV47OOstB3MdTCXV#qV$DRU#ETL8PVa0bZIB11yQttUl7znRSYupF zh*m7se%Udz{@_5s3}2ZX&d^uvz%MhzEJ3Ez6KlnjY&^TUZI@i#am^|v5=Z>0&xq~% zCR=qGm#zjYIdb99WaF^2tWz4fK#+&QlOvJzZXd@P2y2xhbV@H*$io5&ry0Nh=xL6k zS~kTu&(j+JyG69L70ni>G0V8Ec59~Z6KS5wKpPaNZ1*4)?c8r(o~&C?ZX2g_-TKJappG2KLNcD#Rr+jLCDGg04*H~T#?GU_{x9v>F` z)hKN>7_Wzy{<5L9_*GY{6zZeatgBxV$Q6_M zxe8BGdf(sqN-j#3UX;}9LT-A{Q&Z1@%fm>8#55w}V34z#1%f(@wWwGAAFveW4DqHJ z1gW-%1CT>SbGfaPpMEQ5V*0CiJM{KbosKU2sWoepH|s`0GG4pJBp&aRl|Q2p4qmYN z<1>^+Y*dCbcsI9XFKeB~S^~#k4e{`6<2rgDPkWc#-L*-44RaAIJN zyPhW0^YQ0vm-Fc3g>1-hc>wZzyA!xzC4~avsxBo4p`>cKTY0iRPG8UN<>j8{EkRGa zKhZcr03{s|Pg<-)H@@-=e!%v|=BCMkCh>Bd`0X;NlRFtk`-aW`KudSAcG76pB1x&y z|6f`Fmkl=&L4n%2S@vq#lJx0sdpA>Fk=-B$I9)9uGi9R)k3CSY`cc2S9AAf+Bsf&e z2ztCi7Ll8iwCqlr_HFyr5hVBiSVJUa=!&F0(qboX1|I+1hAE3*XA1kSwMrQ-Fb042 z@!Q=4i8XrbGX4k8ZLTugrIl}YbEnlZ9fg?RfD3=1SN*A0Nlr|ofM$iB{dhi`Dc@bW zevq1^FV(s+Fx;#A<22~_r$NxT7n2*l>2kni)kb6Z8wGsd+1=eOB<%B(ZqX0Cy*q-% zn}4j9Vx`Aj!@%(aAhl{kPRR#tYMmQH5pMnCQPS}neVIj?p(&4B@pWZ%dMXb2EAg z!|Qn7^r1VMOOP~vRaP;obi$9(61_WS@XNZR`Dkw@p;>lWwB{z6*1uZZg0&;e(dM+! zxjB;yLp&D}YLLk7w5R#Ta@l6KbaS$rPt&z}*UJg}EEoCpw|Shln&r2M=Be8OWO=RjhH{tO_X~R7rK*>d(t=2K>Ge`fsCn!j0bA7;hg*! z;aGONqy^sx>9R8hj`Qrdau|u@xRK(k5Mfl}>weC!ck(1zx0?NrFX+_k`yLg}OjJtw zn>u6__l*UsW0!-YT7Z-l@<%i2%Zh#qZO$H=kXu1*B-u&(J=;-yIb-qT_fN9n^$Sbd#90Qpj77giaTYz*FM#8gMJVT zd$}6iWLBG)VKQFiT-9-J@G!g5WHl4)t|oM5Fce~9bH`oZcN#micFK4kdB>We-`<^8 ze|rF@L8q#P@*m&>=JgP+4+3TS`dWtqsit&U?l8<72bt`)DxSSYWB^Xdxg($dB54Q`f{+fuH8@t)XwT(E%@ z5U!Uin=t|16M%ogk*zAA##$iX1Q=a*4QWJxF8jFh&|{q9cYwcf+<9-=Ma&cpSDXF2 zor{-#cX##PX6j#3+VSl*FAh6>nq5#oW>*>Vho~+}NZF5mdDoD0*M?ARr3;Ton2p}T zb5p*JG0t+6)mg3;tfmoZrrvn?Vm)Z#(#eOmQN&N!w&c9#8-$lWif9*` zW1h+7)UFpK9lrsm)xPX7T>!sNUOsKK30N!~vkf476@d?^VD zP0wI5gre`_@2+o`x0UtL0s9VW>Wse}O*BjGwQvRzu$ZBdL) zX6d?SKWqIOe}3BqRBW@Ixhpm2eGlYz1bp*edhM2rOhFo*rqJy7S?4KRe%C2173#py zA751{J8oyoku`Xz&&MK0x=3KT9dA~pop-(^?dh9j&`nYUVbT8#3k-W#9AOW$z3&jS z@{o!Oa-M&8s#}C(vAg8%KkH`p4VIFUuE<;JhZusSFdA5{m-A(7Nl;&qERWNhMEz|f zRdcHxPR&={9RoHu!d)$QjYutQ2nB!js*_mKcjO_61`Z~;Jt{r>>?jIh7Z;btucH9` zJ@}f9r}8k7pfz51v3(k2xF&~aH^&$1YA`BOT4D3l=-<(r^v{9e_J%a|M*mk=d+${i zweNTYcZ6<#=DUkZ#k>Bqt=t;~K4w>&fu~EV>Sx0hvu~eqxQRng!ZiMufE$t7cah2O z#nqfl+?kL$4!qk0$z;}fjWxJ#71!tUqm5d#T*}Vdp%y{MUseB~HM+~X_H8+U&bbrE zLXbO$M}Pd9_o|YBYU6RS*oe*!Qlh)s$|!e$g=D^?o|Ty}`imr*@NKIatuQ2i4v5J4 zQC;~qWt+_yeuKaMR?5Tf^@-`erQ;l9JZhVJarA zKq(@nA~jOw8oubY&TP(3?L*I(`uW6r==$(Tx}rG2s7MHlw~_m5QcurNjRc|;#fXtO z5`;)l?AXuMYO^L^HcQhV-;8eFa5Z6>U2e>YDZnL}Vf}703(4srSdDKtSjtUw7Vj zi0`oWEcmlf;ycpbP}bUU@cf}9Tb9(Fxqi}5FA0_b7ZL_m7|Ca8+hU|ZP2b&(^aS0& zuiapBmMim!JIeV)H}Z{}*#cK)I4@mvc3o}wkA;IZJi15x$zNzVa@KL=nYb7vIUz{E!vfX#_$u!#@^19sVESen*I(>q#T8{Exej z*()k8TCAN)Ns?#r0(J!{FJoUlR?fHAw(16Kr&Fy_4V&!`?IonMPa@4-a|eReYKBaw zNJzgqb-qKJMgXf^bDI9w`qbe?_-T{&*RJ>VZb55!3M3HvzX=%s5jvG3XWMbxsq3*S z8Q*wbzwkbf!YEok3{&H2bwaCTo^t1(Ql3H>J1#q*T@I}dL!_eBOD%n;DA+p2EOZMhrT(k-CnOb z{XV!T>$s+*mK3t)l=yxgPw44z{A6sIio(!c3b*gLautiHrnKU5KFKhDw$EdTKi= zM37s^`%0~C4F7jF>mQO#6j1yEgdJMlbleL$Q?0s?9sVy2py8ccN#De9)*JFca*r#aNCf9PcJ0K!s_gRXE28UuvSZz1aNERYDQj3wrRFW{gdG7$hk z*)Gl++9~8C z0P;`ydoI(}TFrmDD>F|l!AJQRs12jjD3b5537_0Tq=E@0;7aZAW^hd#hdg&X-$U(c z4EnBH|6~(fD3guC!2=8r6(AUb%N`P$&t<4pz(si%BnFlTcDl?4*LjmWy2M5Ap;`p; zuG)c*DDH04PXbQIBm(Y6Afc4cPbC96VE5Ys^k8rgGL1l_DK)v0obfOrZTjRv^~d5M zNIBS^)Aa?(($4ZdNj)wDNj28+-Wrrn3tY%q{xGN*kss%@vf%x0l;zU0KeA0Aa?$)3F>2pk}S0Z6v_J$Y3!56FzTTrNRy`$tE{O#aBET%vNTN{4psTZyz!WV~F)}ex($Rq%+#68Y0an!8 zlNqO9YOn@aUxA$d)l&>ni}n-l9RONA$k$U2saNj6K$iyFz9Hx0;<9JvTmMKGzDHm% z?{(cS&FOJX*xTFNXuFpB{OqynaxQe&&nA=oNHk)6DMYT2;3Yq=tcJMURPY>4&@Lzy}Ct>R&c1W3*#`ZW;0%#epJ&jhpzZkzX1$y zbt+geGyy7!SUiBmo3-*b zgbQ;Qr>vvWQ}b}Oa|1S5wQ9>&SXjO8p-&?i;TZCWJ(jOcKvFWb%}fEnVZP@~XT&M6 zfIcWS3^DnLs#1cj`0XNtJ7DKN%So4|Gofm+C+2wGL(t#8H7E@xR94Gy{2F z1Y{^G@uQV7Sh;pd+EbET+|N7bI@TR&qly+kEr6HS6~4c>{Ro=XhK(^q8>Lh^s8g!lESZ)+-UD+m~Ftrufg5pHy~8a;n4#-c*;M z_J(7c@bv+Dm49td17{2b=uL?${~`q`LD?;$8k@NPCE2S0357~Jm^?L7S|D>##GF$y zEh7T%)T0+y-~1N-a?&-6czN^s+C1cd)SJ580YOO*L;1Yl>@W)BbpBCacnPH&G1jGaEQq zG+@E56KNjOa|#(=7Ewt=eP`>S`#~oT-AhYHQa4x9bmeP7DUdcMxpPz95;s9~)nimh zYpM}VjbP?&iBoHSr$>LINSLYBP}YLSOP3=hbLaN3m7}y3P*0<*W;USbg*G*#d0Yj% zth}5_X^bCx%k#HSh$^{_FchI=iHx1y=S#sCq-}%FhHNOo1f44t3)nkus@Y6PHSdyhm{vZU;m2cGp{PI3m%o)t`zIU%R ztcRf6Sswg#AxZ&d$<@HaI-B1~;cWaZOX`1W$NyD)#RY&p6`GDlWB#9da8_tV?^HdV z269-@3-2GjzyAHKfAr@P!6IvqId3M$;U@LG_7&zSkl!9#Y@)djJ_cD4@-_N>v+f7Y$T==&0vV919iq^F_8DZM*b*Up0Tqf z)+?J|0Gq64#}TrN<+cZ^Fa%ruf5ctlH)p&43?>uqCVU_KMrCrP8;At?F%c%cLAJ%E za@H(_3-zXh=5DPI>kNB-**_K&?;g#ewBQ+o=UQ({^(LEK-|nblNSjD^ zGm&vQ;-}6jUX|RINLu0!;(5+o3n^my=zE0`Ck{T}x(80~S@h(94Q2{2itTwP)${n| z*Acgz{$1QfAr_yfkDx90^(`it1&h^T{&#T~uU3BuG{NoLRX7g6|M4XnV5eawi~k9J~vO!wbt-s&m{!HXGLvjlw8sv~aY_ zpIdLmj`=14SA*5tOeQQh++yTOWh`;?V;p&{!ALo?Re2+46!uU{%-t<W)k2Go-ntzDGP0SFP;_!C*Cd=vy&nUhspDZ$ z#ICc69!S*LfA{)hj-gSyxT9a~16YWrpO-20e2LFwD+UpXyM;ZDpCiTki!t1GEjEu? zK~RvB+1#$@;?a=p%;Tz)_1XFwtO07eK$r%Hoo}$Z`BBuVjl*ZF0k`FYk=P!8>{7d| z-a_=D7B7eE!J3CJ!Yodvpf4!S;e0jD`gWu0`3mW>rRYm@C?{9ahK>EIyE|-2Ph0K8 zH_034l~p05=>7}9j?y|ko+9m({9_OmpV0~@|uFCs_ESTS9wYr|6R_y(yM$NB&zZ`J`#!){z zGf1QmC?K!A5`(`(mHB<^WzH8puju!mwA!%?t2}mXmgLQwubgseap_b`Gma-T9iamn zwUuAL#fuafolMN}y=^#z!y1e1w|Z0vcVl~(6mRFGv?ku^=`jYFZU*J4emLheMEKl} zczNAFBeuI87@W@LCadbBI=uv(uF+RG<87an*>91S%HjRJJ$O=9F+uEiIhS#IciL+%vp&C%G*YB!@lq!k34%_kwJe)nN_8X03qb|8SvK*WYN`3C#6pX?(DNQvd)gmY zUBqI~`^PhIuDw1&LNl7sY*w?QeJFSjMMO+zNXX6Q>X!QN~zb8XD z(L_YC{JC*$!{zi|r+VYsbAsxd_p@!D_PcvDkiv-+zCyr1q}Y>O?Ob)}iMgA>8zHi+-siP<;uNoLp?z#S5F$i3=M$gOprs z9!EwbBttNGH8B-KUL>T|F(8v2(eVBln?|cKXN=xIsh&x_#GtOmvvrC;Z~Xnmozc(d zX%N`!vQ?Buw+%MnuMkdOSZC-=c{Q_)i3yUWizL{#CxLbQ!L3Gd!-G%=7bEfae%_&f1@~jpYlwPyMLri;sy><4H{Lcrh}g znMh1T1*iDPvw@B%$a2?BBBMt;!AcZ*A40_dHc@dX{pKKr;Mc#9FbkCtn{CK@y}YEH zF!Vn8SS!N)v9ixFFvlt$&wn9Lb$qb={rKvKgx3r3(ip+ zW6v;l>0$T(5@Sy6O#aDPwu&{DSNM+S2oUV&GCF?|D=9#OTnMv}9^e<-ZUPrJ+Yq`e zVZ6V*NW^wO@I(=34omRdR1cpl=2e9toJUwr*rkLLhc{bVNED$QUyK%L?g$F8*9_LX z<8=M~TfqR77*hKishSX}dXdng2`gvRgOx|)%=y9-6kw6J6oU*#o*Tv{o$Y$0Vg zm^eEBm)(kRHQY9l2(*8(1Z^IHbX>?-Z>H5h7J1$IMAoixfkqcY9qxZErhM8zPLYJ@ zA|JQWIAun7eE9y~FW-In3!LsLU;TQJn<=da*yB_*JJTI;nCF`b!gH%;QVz4+bJE!* z{m{kND+A?tQo{q;TecOO&&dm^`b}HhKKu61tA4JO!eq6fBd)G6p<9D0HQr(v2A5iX zejPf)+q-SQG)NSmZ4X=r|G1rc=?v`4Ssj-*J%U>91xZc+Wc9X_O9YQi31!Du_Mq-bjYjsTJ=kZ_Tmu3%l#0osNc3_M02gduKQD!0M9KcJR9;`^@nMR&ccl z#zGJD-V_2@O>TIBqht*szwgn21p^;J=I!|Gu^{Hdf>g*|+}N*$8ZZ!Y=g2NmrRVyh z^%_7~98MrsY{*U0i=d;1?gVlRA|Vx#oLvA;ZS7d=G-)z7d4axrF?}E>8^ii^gsniZ zRcNw`;HqYxWZx7Y{b8X6P$DB&@!{7rlWW&qTZHn$JB__SiiViC)h@DH!6l;E|63it z50M?xz8UZy9-BQYH1ub|zWE8Lh4)3IB_ti5Y^mJwzV`_d(m%h0` z?ku`Olv!map~e5b70^w+z14|$H8mz!JLI;tx9vun^}r z+m2F5e0_Ad_rB##IQDRebgC*0bzClq*6<6Zl&_ zuH=O8VmTcJqa}l}Zb0DoABF%YH}lfqtgE)i&^z8n|vzTZH? zwcZK(d0x2^3y)=Wr8Q{rcD!BEak;^L@IOv+^F>1)wEt3Q1d>blU%!$i9jT6|f*Lj^EFEdWx!V zpUdUE@?6W{43EVNX2>$FX)T5C{z#o1jM>aEa4TlnOyw$B;qz&UcHiVw8`tRMin{9J zRv1t?mxwTX-cTO?QP1i}7%)Z`bu@s<&S=z6-)K(|(%t=QR$<*mnHXp-$r#IRsV@fR zv{7KL%zh`sob9*EuO$UAa6Eg;RTaCX<$O8r?0U<&I#GQd{2cGg6BSEJ6qNT2R%;-k zp0d14(>@3+zbCZRF6N})`SpVYuh|xqG#H^cb`tw>{8v9-_5Mo>z_jwaS9@^dPc2#B zRj!azD8y!`4OiPZ>tD{0 zswDYodaL6NQ@J*jbln}S>bkBph0ATtWF-HKv3CHj{M+^iW1AhbV|Q%Zwr$(CZQJSC zw$*XRwsV5X@4k2E|L&`qnyETfyJ}+QwJglBGHg9y8Qh(ywKRvQxfuKI|dQVVT= z1C1(1|1>mcJ54u)8!ld{i*EiZ*)Fhr{2PK)?{dy4wCjAv{i3fH7&rxozZvLy8R)(r z^9e=tvKM8{YVD8wEWO+MHATwax6Q6{bu(4Gkcg>wD_UGse03U0)0ldsYQCyy5iy;6XYiG}6Y6O?E19Q@tqh(I zAnUD&q=bE)=1#)Fh^uc&G@T=Mi#kQXx8xCuKxfg-)_yrsET`SuR1}9@-I*S!~ z4kvh0`j7B6nqLlvAW@iQDo5JqNqbl>*Fk7$ugt#rMWK5^h^}hBy3JHk9`}`^X#cc& z%nCJ!TGp>W-6D$|&-DMX-d-Xh?3;pTBgJ1;Y!0$S%10%M?D089Ml-IBrl{?HQWw5m zomm%P8M|vG7e%veok^zDxqW71G!|#ZlliaOY`aP)f5(QuD=Atmg0X~Vs-Cbh9YEnem!-P z!VawR$IgA{jgxyGkIOaDc%9+jXF_F#Mnses1~i?= zi%<}X7hp|b^>v3GIoz~8w6i+Tu5~gLzBh>Iwpb>|j>iRIz%>5P{kB>$zF*bj0IJ?f z?K=U?Y%0cB&awY23D#(GRrS?vXG)^)m99lm_@`Oqna5tUT$Bxl_xt*Ny{($k^|%w| za1s1MGcZ`0@NG8L!Wr50vn&?{CzaSXJX(~iGus_detY6c^~`4H?(nR_temp7;1=Cg zMjpPc7>x)e=G`9IutVgB>K`SzHhDB{JNbmvcz9}9nka~qE6Zf%e>&kldN{Kd83VC_gNP|e+-zf zi)wawDoDGA7D^>L^@pv}D0KN9ftVXTWSw~Pobq~8A#OOJ;$6eU^~|M!9)WocXVoQ2 z)R2N@VRuX%2TtV6JU zH}$fo*9-hWPqpLpk^BUVzJSlEo}*`P36`a})| zq8?6koQNea|Dr9djW3lM5Eef~Jh1V<3Zi3O(yR6*b<3fD4n6+}R=EtTiiU9HXSm~8 za3^>PjB$p^LJgB^djya~-(SHPYR66O#A~7fmqtu`i5su*iCF`x9pQ8ewKK$WRo`;E zh!I#o4qSOZlv}EXsb^P&oL2fRbE;uC7`Nx2;RTr|?0z8OcIWNQbK`ARu6}%ns{l0M z>&x1|?H{t1PC;R4mNYx&WhqM8TKN`$9PKO>wkC$win&+QPCP!DY@B@Pc3XT|m*rEu zOU=?>UUf~o0o9yc*%y_Vlg=hBS)aq2gCJWOSEVIQpQgT_YLb_4#xVi5GtLTm3uVGZ zLhaiO{5F@ImluzG^v`YFZi7JZba!(|OvMQJhLsrdQf7ms&5SPgxt74q=7jkaY3A%j zbJceRQxMoblT1?Zpvf!z{HLi9Ae#7_40bKFh^p8S6vJYsb#@5srLogW9Gpq^|a!kHzXnx_@47-s_<$^aNVChqwbU(M4Eaa1di^U^u_A}wC}&7pIb z_qlo3tf}tHnO&v+FX{Rl73D4UZZv|72pymE-!RHXgff9#oG%Qt1+JXE-0B859zDe? z-+;I(T{{0&!)dtl!^yM!%&l#jrYX?N8tIcGsRYeugwBNH zA@rUIrd2ESW?9HCW8itZ9Evo;fu9r#q>WQ3rx+11CD6PvB~tOG2@*}rcjldIC5ZoG zm5(pv)A0Se6!%1HjYvUdn^>Yo8I42K{X)2uU;jg7{I}%z|19!NVV5q1*AYjeG5^hp zbGL=UUSFB;@t2M_oz+;tuR72z)Ho33+dCM_`~8mF34Wsi$MUFl1^Zeft|GtWIeMw( zKCa22jjy1NW=4IkU`n54evDnCQK{}&s`>FRRH#f{OXlP2{ZzDyq*uAeNbd})m>%EB zLh0Xx*WQQ?P-5}fELuFen4Z(vx9fCU~6nhH6;*x>-ijWIn`#gSHGh^)H> zYW0|Pv9GxaS@m?~=TlYV8Aj<$z!VHzM8)TmP*FF4Su_7L;cwYW^|cPte0YFpGxy^& zIySr9FR7(+)flufwEzKH*-u|7UhQWb?OrrjwCsU+;efaWr~n9&pu;v;CySdS*EXx4 z#c6}POl>~kQ`^9~`-Z=&JkM|(W&&c>a<)Y57q4%IdFSGsU^@Keo(HCTg&c1iuEkm` zjroC1PcV18|022$79ZC49z1MZQ-^A$4w8Fy0UqMO{FF#QuoplUCJYu}-03SSC39r8 zvER+erFDTdTz{Z!h5JY9*zYrmQ~^qU+W2R;u!JqY|_$k}>NLGjRcofTd_=nuL8Sm+yxnY?qiiY>07 z?bp7dxAeV4%r*aXQPYI*An+F0HrvTZPhm!+Uj0GfRe3vkX=1gK1h_gzqc_i{QllmO zhtk&ULD#cK5gUvdn4{UT=kyc#mlcyKV9G~4v9jmM>V@cgWxiuKLZJjq@A11~aD2x9 zKdTB#ufo2!X5bsNqq)v)Zp~`^bP;e~XZOaissriuk4~&lqCoT!Rj0D4@6`-^Pqe0poo4_ZfpYWa6c_5uDBoEek)4E^h?g; zvo%3Jt7|j8k|*!I2P2Ck6-GtoFN``Z$meZX9JX8!t|)K__;1yl z@NEn%8Vjr5jlNnNy0$mts7sALod<`me7oulVXHqn8>`P$IIEa-uWT=`(g!oR6q?a2 z7Kw<6d}q1ECgkNoiAYjXw%1Q0!Y!-CMsAyrT|NBkebg(~azbvHFCsVxI&d25Pc-`* z8>>mRxI?Kj0S&!eIM)@DU}}iG3X$m)`*?J&!lvytZ{-=!$icYa!I7%hGn!fCk1Qrt z+*v}8ir5I17A6)$Dmh{9fVc}K9+u6nwJO2mJim#&rpt=_bK&(p*}4(3B(>>EB6Lr1 z`s1#t?@6;ei!G~P3FjA~*u|QePDzu2$+DlD57nkQ{qgYM((485kPX8rL$-<>RO1A^ z{yj#YHIjToL+C)rRFCFT1FolEGR8>Dk=as>~{l_nL_uNzs=$ebq^g->umm71(%qVfOWw%hJXY1v~zL z+XV0UuOr?ohl}0M2f%Cd!)hkEyBVnSZ#!CPb#?lR>GThy$`V}{%@Iik@+Ga>mG#Yz z-E+tJ(l;&0TGr9p`&FA3JoT}w>fY;@u*v7!z%ed>z|;N@`qW9TPbq0?l6xEKb}#EmgC z_nhzQXC>qvY#-~rj^}4~K)k+*D|Ts|Ehh(*nos%5CAnzN8#x(bGDde~x*r^bey@|? zVC5#m27{^SY&A@AdICCXVjtl6JW7Hw&l~b5`r2NQ_fU%@8LDbi%>?UL>?hlx_23MQ z_%2;;)zt{C4DBo76R2*4ZwLcR^@N?pE0=iOoPz~A?iGR9?w|gbR&%LvmdXj&*DLKt zYJ?;VGf~2Z=LYHqFcBm?=c%%g3WH5t$6q-$IT~6$sVLoVML5fMLZ;(WgUE>Y&IgMx zVC~@YFBpvRfyD&5 zrmDf(vq9@M5Wf{e%cS1VK$)z{=F`J~wC@JG+(_>;W|#Q=h1Dn}It&0VV1^ewk5>mO zt;&_sU01i+ZRhw8`ABheMPlHfsqlGD&G38SD+_&MaA>aff%#&or%2)~FPBRKDXDO{ z+jfOQg0b(s_q?Z>m7R=%<`MI)@DQrHiPctW!<1>;ji9)6wdTmT75(`FnmxlwZ`7TA z&%{E_ESu?U3lqb^vD`CtmKGQklqEpfwuW||HEka`N-~6F$D9LPx2nfC=&#mC1>K50!r>80aH*0FZ zPm?aTwEwIUsO^3k7Txxm1Ock}%TcoZ{<2wE?B&;GDI`|683_mI2&%)X%#`mL%%{U@ zh;&{Pib(2gwI-}_A$~8S*4H+TPE($rE`Z3-ntkcU=OjjMMcKoBe0EeikAhmElg%gs zhWus^S-i3}1!0@Bq!ICPY)fGHNhi5x&0nrH_RrfR8B0^Ti`IhpEQbg7Y=4}r?T>uW zR&M3XvWD0JAg~xbjUrUMUb}7(8-#NjbJ2wh+^XbM6>mjYjVY>6Ukn;DEH_uW6A%`r zY@t>@#S%n3bU9t%To`h6UREk*q_XrW%2uhb&<4j*uF-y<{S4PH`OsVfxwW&$;+Yr3 za+p)2k)(}A`0lt3M(Wn+c33xt*RyR3Sxk>6-r|R67K&AwgPJb>?Jr*~bja>@A{ATv z9rsipU+T#(FKQp|cG9@slS*T0f zBw{AeySu>7$a>CxD&EaO35RRU=cEFnfR5GwTIiMS?51T(KH0_-NR+3h3fE=+O5T9( zdyn%veZOh2s0=~E&1MW*O4B3$EJ=&#?mRwc@b=20!*}o<((L-3Y|p>Zj_T&? zJ6)}9ge+!B6&nr5Jv_{gduZP0TL!viSYUmvo9*G@cO8a}I(Mftj#dZ2cY=+$yT0n1 zO%TY?BaZ6z0d~^`haS8xp6#nI1tk@EiC-zgu^HlZZHGuOZ8c~a=FjGjr6b{;SpThA zUS=ug`W3EwVJa_=KlyupXdIaReNcStHQRM>qI5Cr0`Q!)VuW@yKnx;e-R9VIs{=sK zAdoQ$A0u@hwp8pu9Mn;mXZCMAnjZ9UV?%k|am$X1|G^gE%NhyehUy4Ven;^fTsatlB?ZRW z^g6Y?idksK>`Xe|Eu(Ng=gsIW^%IxS06+io3=$mghY({-?Z!EwNlW;Xu2p_9CqU-s z#RCnwDgFHvV&JnOnH#8^E7t6B=9+=;)h>JO(7J;)v6>--bsP`d4W@P+B~Q*`vgOXW z10T-ND$98a82c93;et-?C;MvNALW$Gnk~3OytLVEr%g@vnBS$}F+Cx}SKs(7o>(+e z6~RP-6OeI|`Q`Q4NeoZ(Zle3&3^9%3d!q74IAgT}wBk0v&=s6`sb(nYI+32D$z<}6 z^RokW%x9HxoBV(uF8{+6c>4nRHYqFFe^|gtVDrOQICgM}m_Lb}eE(+P#Tw(u;7EhZ zFN6dqMe5W8P4&XB)S;KPwY%m)RAlZZ=c#`0$PkplXJW3j&uusW#KEa+ zLjjcw1)VJ(S~ERRyW3k!0kVDfxPQ|XH2frs3R8rJIng&5K^!N}h)jv8y-l}Y*d?|X2C;`*byU$DmIYAU| zsNRFX!nCTUUt9_UgM)cd5fMa$g!#I%@;8Uie_Aj2sh5#z%?E6A<1VPMEVpV)AE4PMC zYi;fiBHEk=+(x4@Bn%8UU_6VIIz6FXay=srPKX1S0*&eRc#=Zy%-$U?14&6?D1YOc zoxB8V@#Adl`3{t}NOi=_ITIzoxldyGx@pxKZtP+r%g1-X$@Fgh<=TFj!lRIg#iBKt z&0<(sSO}hUQ0TTTbxyPl65)Tx_=A7sHNaf2NB2-IXNKX+se?n);DWuvu5UUvMXrG3mL> z;C9_B>`uG^mP=ip1-`XPl5CBAk>P%f!VzfQ+$$nYRs*3US}nN##6YCLv4cTx_Gdrm ztIdq3%Z*7>C(#D^0bL#bmkhGL@u13gBq>^KRCpC-Glc7Y!Hjz~58 z-c`$z0A}(g>N^G^WMZ#8%EtuKj!JUAsHBvXVS31?+rxey;oz`e4tv5q?{`v>QQfMO zJsyw)mk@n9YX0`^7Pk**-khy`otc3&gZ@3Hx*ijP3ow2w^e~5OVKxsZhMfDckS=zt zRk&EsQ;RHgOs{Ibyt)wS?@`{=ho9#~RpKs^Th=|8~2_)lyd-Ba5Uat-3s9?YtsWIj{jO{XqRIL@@F`0CF-_JB{N5^8t zv=V)QXvh+m&B-Mhw5Ms{qE|j(5Wo?F(>-4UOpHU zx9yniq5i2QrjgORxl2L5aX<#G2?9ok`md{Wz6Wo3a(H-t*AzVT>1A!bw+)TUN#Ty0acI0pa{F>RO3*BD(@_CsV? z-j8ahf>m_^uAnvOZXtY7RINFe78eE{?7RlzmVp%4C*srFwQmUtd}vOW6m_U4%$u5u zc^PUc7HZ?0pGOyG$L>^!pDPjHRHUatMjYR&#?b~*! zd!neEqbHct5--bO0{K4v!GrbmvhSw@X9WWp?-f8#DLANF=wz|ZbhmR)=<-KwY!2vs zKPW-Ocb^3Yi*KXe?uJ}nkWmg(TeiBAX}4^+Eg)|+hHSayw(jqBeI0DyQb+a)V@b>g zVg)|d8W9PxOUnX5v-?jHuAlZQqwec#enwH71cuDqVd%;_mhYkhXyYfgNR6@yljHa8 zwDpvxq{ve`8m7dB*-+zvxt_5UoK6KGBK`$_G)nE6>z7d7A@G`x#YK7?7I*fvTQ1Lp zy{%GwZvC9rcs(}AL&}~%m~Rehdy-F-4%JR>Vqne&@mADsR@CL{Cy<}pHaLF8goHi< zgt>wlgC+^15-0ook&8%u=(T;Pb>O(fvudb-ZP@5*KVyp zyaoRMjt>5p^K}F{yY!>0JcGwwL9c1#$A89_fR%R&=`@>LJw!HY#JVlP%Ko>lOZ13h zL+;JI@J%=W2m;O00oC~K8!iORxc1I%Pr`?|IhJ1DRceJ+=#%~4q{IX|08{1NOG6%f zoV1>}&&(Esk?4PJ^y{_Zt29X;C{zUPX0b0udzq>8wpHGWkBi<_qSQ=YqPay+4QZ%} zYdulZz($+4cr1_$Me()ZE7fukD4RfW=sh4S9W5`GIAUa85D#?1qv2kc6CzpdRTnBk zNJF7|XDjz&KtFQA8Vrzzg09WqJ0l?+rpAJ_5oN>ORD|a>={*Y{ud&izCPURaTvjkP zickO>@zd{d_al#fSKl;h@oI2tFuLDqAuNQgNGnpzt9kkQtczB%P@3oV4#A`B>Uha0r*b4F28~B|HbLnrc zvF4h_c)7XPL5_sY*R#O`G>Fv~;=RH~_v?mLC|H`>g#|r)+kPCtUcHs%7EZ`#LC6$A zea828$punY-55-4JZGcB^%Yc8;OF?T(akojTAE1M3u!~#Lt68eyz4=ZX9kqAI9cz@Wn3VPB%4}p3@GTwc|FCVj94~5nxK^{i zZ{~ctEh&RSV1LAUf0ae0zp@mMVNKIh^0_&4v#ND7(-i?}p{&<;)u8!Yy}oejo5l9H(pCx3}c4RQo#g76hpw9f3^P zE=YE@`E`;u7b9{|jY4GEA44D3`0}wRV)N{mXC-YnFqUDX*tMq^qNhWv4_W zNAplqJ?FjYo);oi#Sg_UO{`=`=gZ(dJhfUoPs-r-G;OuvHS@;;U!?5EWpJ=Wx-qJk zl4QnnyR7Rr<2`{fwb3aKq8b4ElLl&UEyqTp$!n1Or?Gd|ac$J}K@EPVg;mpPIf)r2 z*!{8{$)Bd)194b9w=&suK>y*&T4!%?Gd13edtgH*?GPz{=e8@&j6DMG(~QEkeMW}! zxFA})r_%Qa-@)Ew(po!nss+W*IH?-d#Kkbh>a(!jITV`OwzrbD(z8)yA>?n_&Vt9| zBk23H4rIdbg4H%_G8u$nYsGld0Oi5wOPR*l>EZ&yc%6=^bFcl3wpvJupDmdtg#jv< z9N&4{Q0{`-$KAO{ukH@2Hi!vcZ%}z_;q*LZA9S=%p=^S+%STpu~9q~Dh#!gltlivtj?8xB?1?6M5vtOqh zl;J!?lZkGfo(=YW~v7vgtF>Yhsm~V%>xrP=bbkXu`Os`Gu)G- zF9B5^_lu<2ba-)Mqs0@+YVNX@Atz7-XopL>ysdo^%a!#es{)+DJ+A9cDV}Jhm(m9- zk2oDHr@HJg<_$(mgE`YxT_sWL z&I74OhYXv?)M;Tbngp{>;68nxxzdv>E8mnv!7%GM98)SkM}Q8M=t z3{+FLG&;-XT5CH#Sk4g)q)DMQWt?o=0!flEG_SMa_89bf(TKy!rwI!=c5N{+(VbpQ z&@B1u)+%w>vSOhk-GziA#rC-OK`@$_jqkMn(fOOVZwuA;AaI5&?h^K$o+WbiX+-IY zqmwesQis&SuIb)8t=k!%yZslzOoXwIqL*W2mq_BAKT4q>3`egLN&*qMN(+zq^KJ&7 z&6@fT_p5WqNYXY&)*t{X;(pN*P4I$qL>%HU0~g1WT~aFqJ#)*03qy^OWV6zdPKhsf z6G_7eky_Ad3w_MYZUh2AE=#H1Im1m+^R~Y~)!c>qr&f0~?|V9O|DVP5jcE~J?Uqu= z>nl5zV}?i0H+yA>FifTzB_`m?unfT7>zzI9*?llP)5uww=ZzXmBW~&&uZqu%lc)3q z&zS}wMRY!FrsM~rF0*TbX7^nt^-ZGG<1bb2OcW%-1KwjJ&v%RBVAg5SF`RhHa<G)nK9yfDGz{(Y&L81DYZ={OSlSjY@v}Ee?mG9Z)HGu7D-F z*UYaKO<)Ow-SW5(P?Mn(Q6&?e$x#;4D8z}6~Y-I6Bi;&ZCvv>e}am83gfHL~y%6NGa zSp1=dwWJ*2r4yX|(Uz-v6@iQy|1jPKTFq7{U zrCkdV&Z3w&BIkD6Y$Qs%dpC;okt|DxVRjzQf3q(Ry}p0TUL^5NnX#wZpQgqjlJqgt zm_UJ18(x{TBGx=Jm~?z>*;{8u2+S5aZ1$O&TBTZ7@dmr9TT7I!Sx`t{EsW?pDX3#l z%s@uov#TY^ZN?+7vBz0!sXXfK2`_t1P(zffc&hxn^)9>Alf11Ll8UPVRc25yjtEr^ zN`MD~voPJA$i!Vfw0yDCWtcGm?&otpa`?e|6pAO$AqT92^>Gk(U@R6Xb)?~d^`lVR zLOnsQO!zVR%Y3ZWOPD!FSi(DqJf=^0W+VEkHi$=Z%bzWUa!1O{YX)a2F;Xip=@t86 zq&96jXPr@FrUDCW4-XG@8Vtb-eyWMlkZ4K(Au$h@GvZsn&Wnv+Y;`-1?%}2*F@F0` z{2g8-2C!B!F&2%6m+yxzob$}^I;-<8cu~WF-0z=wXHSO|I2a^r zQVAtaOJTwB^URQB&s4@75lm8uqg%?Jt0Ow4dYh9s*2FZ%@NSwG${NXYdvLxuNX@F>C6)E(1GeOmIWToe| zoy7u#vy70&2k6XB&&Z+kzQuCxovn~VtN9jtppp(u0nLGo^e0aOI=aka{l;Y*Oqx-X zB}Mxy1z=+SLvT*>cfY}{*reR3Vq#coP9!*@q_S1Blv%JQR(3|w1*et!BnwuW9l`5C1d51tt8e7UIn&;!-;f>AZ6KSjE9_%+z|#CnqIz&Mp)=kim9|M*9Z^vO zH=&E9CM2yCTI+=zxsnVw3(1`XuqOwf_)*rvQ`f&}-uk_+FRq7hLM|)`U#wKk1*411 z@-H*>`LU;Nh9#K9z*rC_&IYkhIxnr{e4i^3SmtPqB@;>TcNPd^jgd=NFBuNbnMA>< z0HW|^zP+!{qQ?zEL4PsJh>Zn;P9`KPd%Iw3p;Z@VGuFu5AXB-4#BQX;XGjMQ5d8_< zaPyGLO7)=flvorDr7wyii8J7m_sYzI6hqr%%_ z)pN1KBsib2o7tNH60P!ej?ZfeOW$+{x>;QT}C}|?(6M;cSJYZoc zW*MPG(ue4ea$Cr~)wp?S3^Wx8nYu*qd3k=<^W}8!{cg^Gf{H1+N2NrGP&$gTwzbEDe2PFe2 zi2*E(sL0sNEX^jG5>|fG27(7gnjV3afRZjUytp5cUYkCDrd6Gj9>J(dZ=4AbF5mJk zuxlGpeq+?kd%q+5lVwr#9_q2%<;9kkv)~ujrKC7zy(wOKQA_gsb@^!j-R{XG?M! zp7DrC3Wla}Ed-ArMkYp>f|egFoS%YW00Y;br2FeSnK6N!8^LY0 zpp8?$_TI}9hP@zeoRKNKVoic?_f)B)g z!a#hK<85*pA$Ujux-p5(7ie>lS_zQzw9|TL;SsK|&Ko1I6kq9YfOvLXcbukbaLo?I zcLEZ+G}JScvFe|@30(W}9rYWM-rlZ8nOWDfR*SyhMptG=i`NnudtmnMi zm!7*3(tWLjcuEWFXRl2@I5+0DLkSN9r_RJ$-j$ciRjY!fz9*$ECo8<-EwB!r++&%2 zl~n4IcZ|LE=!r#et5L8w$!NrahVdWo4Obh%E<2^z6%FIM;eZ6>6B1i~CF5$4NcJM_+yWRVH zT;nYtRa50`dGea(l*JJ6upJh6VTwjOa_>Qiy|sJzSiMw|Q~f*mp~gKcQn%&P)rpPV zu;aCM^%_;}kalqit6Q&oI&4S(`(&yzSnzQr|K?#*(i)(ToP>10tq81ZZ=SNsHf84v zNIAb4N&r&s90t4J2TD~HT&o>}sh2?za85BYKC=HJ6WkhhP}c|R(x_;`r>C`Pno|D> zNU-HdgAL13+mmu~vcAm*p3A8k#mpT<%~URTHNE7G%SYhv85OLkk4v3|zAh*9GrYQQ z9_20A6_?`c*VLW+>vEr@DZax0HiO_)1*zm=<_v#3=>;_%l$#svvZi)yT3-^WFt)r4 z%Vm6S=EE=3Oz4%JT}6%;Td_&z)~s?NUFe>D>^KIE&ZZk5W1AUNK1ElR5{ad_BWj=) zhQK0AfPz6kf{Qab&>YtOvI|cBk=uELF%VdNFqJOwjZ4qdYT8;F{x1mm7|`1nC~5G# z;D395;IkV-w|%{)&mlgSy{>1T-|rKWLw#_h;r$L0Jh|FzZuG5#AlYcfhl*s-OFUDSc|s#{yVV6rOAn#g9Bz7`8OfNWd>E4lFLe! z<@FZtM@vgj??H+m{9k$g|7XVkLMDnBb~9A+{x9#9{63UyFRi(5e+8-VFVIX@8_j!= zLI(_f_#*xP0$4wMfr{t+QyqDjsdZB1tM;c$dFKBKpQ!cwl8z2TB92ECaGY0WJ_tcf z-yZBk-!1$Sj+{O?pzb$}o@~9g;H*sdysFngz|Mb1U5Df`{!@An!~{d%wP@gV_tP+I4HXDIFCmu#^qG6uM2YiP)ng`nVj#|N@lnLj@) z;`)C106CW>l|!S}6R`VVIkHq>B*K2ZC;f$`%h!Lj1nF?@&&Z0B^X87NrU@Pm+Kiv9 zT?LOX`<+}(;759SKO52etV=8!vv&iyZUT&xTilP4$k_7GbL=LFu``W2SfYdx*l+&<1NTCA#5?DFNwO+;yt%@n>k7=IZWP2zB)yW`N-dbAdH zIXqVCvOpcyYKi}*U@4FEI+>=_W{VeDXP0C}w9Lke@l~tiP+VqoJ{R!&>ocvMNC@g| zE9;%_GoP5HIpVQk6yIonDB{KKDthl&>=}`l&|?Hnvc;O{t+Fapmq60@)dctS^df^b zW{yZ`OrtG+bH|tDby-aT6f{elK+(2dGcT&=%~JdI)jcwD?CsNwcr;doHH&hi8%InO zqW0pVbWt#|l9&<(xkxmj$)2cjSa9j(R=hEqglCKE(nCp+gM}eJ`==BX9`}#s)wJ?S zI+An1wZq{dvePt+xD2hL`Ta=|*z={-Nj#CdRtu^17?S3eCpGX^$NX8UR;Dg*u^0D1 zLTc#8UjYQa2jtvBVo$$kyx0fl&5#i5t}D(7*Go%I+T_RdM$uudlmw>}U+*Be<*0y?#vV#4*>R`XSUFG((u z!w%nIFf^glnevrd70GQ?$JiS{>ySiF!dzp41#XO`p(3!F5L-)Q#A;+SU+6G%^ynsP z@c6f%*m09x+0Snv6mb+6dtxL4HKOeR<(r`%>Uu0SMG{w@x5PFN#*L@5!hUgOEyeMb z4yUN0_^s-oRckIE7;;FgGVe}}<$HhmI{LnXo4K-X@x3FNnUc+HYP0J2`7APRgjA_D zUF&@jKMcSo$dIk`ezFf|u{8lQ%%CpTP}dACzUO}zjm560G_KbWSO||(-(o3pyHbN@ zw=I2UmdC{X7%YEjP&x`di<#WROcC;tpEcCi#dFN#j0LZ1ljF*>-%Q@;q+jN ztfSZ2_C`(c_E~hS{@yU7o!-2Ys=8ABLrG$~p7gQXeT(})M(&bQx8oLulrOQ6_%vG@b5@y>1vpw=@j8@MH&0URc9~)mf+`g??3$EL9J$p0B_^z zBi_6gp2-00oFn9PB;RO*lVdtVYI&iqsOtrPT3DJQIM)9GP^veD?+1na=UN}#*i#|k z_MxB#6<4amWU6CPJ6&lDzCD}mNYT%qwQTUGCe)}l9?g!j(IJSbFH#v*(AKsEKBno# z`i;g0!{bHjWwBcKUh_JqMb+AMgLn2?Yto{zdb6*bn6Gm`{oshg*hR&Wx_Lcb{LK3T z6X}n4%4x8Lu{Gm9+ANO|to)hpQGpv&##ZM>aJHYX+B8wG>)YNVmAKk^o zbT|2@hcC?}3*kGFAvS$HY5xf?^d@61WD(%^$m*HPn8*`x&(_sC|BzpcbG#% z`-m?dsufYfLd%|milUr?h?fSxby2`T7o(d>v%zFXGt!ai^}6&61zl(6d*uCGijO6a zXAu(LUVGEpytShH*E}P)gAU&X9hc3(MtpKtK;eQnlZ?s5GJ?}iy%?ne^&BnCAWB#v z#7kqlG2*1u$}g8W<%y=o+eMkKE?+X#PQ76fA5?R6*_ux83^Gc*@*sY zufe&Ba5XJwl{ijCQP=yOq4VXk*{UJ57!?O)GMWDRU~;2p)c6kL0rUOb_fv8$c7Z2$ z%>TzWZJtcF9+&NHCwSFbn6d>qt*UkL`yA^JR~EjRt%=v5Q2QNNdZOUJ_MZaZk120` z$7iM6^dc;JwWPo99vO~s!{gXQx?)m$><8J)@9w-O zxMz-OVW51yLZj=K@2hM(q9de{#gnJu*CkR+nJzZU8`a4kB%}A0#<$~{(6TiF9aTX{ z+QIX5W4=9;+i$q2STF9Zi6@yh2MmYJo{KAK|EVkfSxUIQs=1JB>5SKc(?tw2FK!d_}XoE|N19R<7IR3ckDuhwF{J1 zAh?n+&@ceZ&g!#TLTAFH#x9ia7YE_BnO4+CvRX2O-Ja<7g&RI{k7}g0Ee)fMy;PvD z5CoF*sl1~Lo2CSoAE(>xL;Nvx=fkvRn8dPc^`PhQt`&=e-xq+Ap3YeN7&`gzaQ=A( z06nW6$W;}*u+^Si%>@?r2ITt(;1uA+v zqi5Sq1h!*62U-AI9VtIFA^%2f<`-Y0E5zKb=(j`Rq+=%Hdetn=)X58g;~;_EsNM9^ zzOH3xW@HDYVXNf(e#RU5DZIL(1Yf<;=m?AFk`0@`>tzudD9GMK3uhqU2yYydY+D^o_`J ziWL>-t!6i!NzGMV2etY}Lm?3w{}981)j2H`@oO8V4A1N5QGP7o@#Qo6!7+rlbOU1SEDPe)cekvfejx3c>!9hdo7~pPw%x zqJ}N%8eOY`GTX=#6YnqEubeK9G>t@|{{BFu5|;j)3RgMy^!HDG(!cf4uoWHxNmBof z1rQSOAP2e}qY^REqe@jLjO*QXktI{Fhb-Y6|ll0lCs^`azJhjfB&KANa9 z&32ciENRrRaT(KM$DT@zS`2?sH}ALG9;FMWZ{}KYyfGI^8`hwXSIz!Ao13a0S3PXm z;BX-zO;JNHHV>`u=)D;(99z1%Ju$6iOTco>kKNIpJOJ49SxINt;ZIfd1)JT`dU`_o zjS%_CeB^p4Ynwg*B>IuqVs~6wt7=}~a5!}^7qW3Zhh`^TCNG)v({bG-IE7O9CwysX zPvm(IM;4^#*4TN89BP{mVGUJk<{OW7bx#Ja8na|G#Bj;523Z1H8@$%;wXxq99~D3& z0_MlmYbh37hBtyl0cAFZ7dE{Qfc}#@?bdeB%X&>?snk$1tMJmI$g}C;HREc`a|@GZ z@*_jRN6t*{ly6Thv>ed-3X{TBhG3YcVWOw?Q9jKaX|HfxyuqS@oX-{}C~7H1 zO&%6@bY$9eHkH#+_n9^$;owO9UxdA5bfrPFHXPfw&6(KQv27=lWa4CE+qUgwqKWN^ zZSL5%@#euf-*evgto8l6*IwOy_uAc6eOF&qb(NFsSAsHo(PK3A7Yk{$c)zPeS7BFM z%Ju`7vV>Wj850E~PZf~Xtv!OGhG}&;Zv$1=b<&Un%jTdKKDY435KGImsZMD_!PJ}5 zOhQ*Q`h#s&B71OaLmXuBvZ8Q<#^~FsmfJ5f2I!A8HZ zI%jQ{RRKy4duXikfU`Gm80g6Kg-?eVb1RtdL1YnRm+x^Sj5Q2)u9S^HXYSO>tMd%- zmMorXE@OxQ5<_%dFHaM0?15Z4_eCM1*L$g1_7O$1-AJ3_4e&}ZgE7%wm z@I$2Tu#xh_V`6SU-p!Zb(8HR+eM{f(%_5C9V)mJvC+8Aw+bL4AxH-K~d)J7lfCc}h zqg0kOfsc_&8hx2$)j$r8fDv-)Zc_G%=^Cr$%j8@ph8` z$i;S&@w2Q?xhI#Ynad$!>0Jvb^LFMD>K8L0bFcbb;h^RMCUZv@;<>bfwRc0+f+_N{ z2T3bE^RKBt^VS1K3Lm~!4s>yFMPOh462;Ksr-4$%HuIT6dFVPq9>BB2rSkngwE%*X zaFhDOCMO_;Hj@*3|0qhP7~M?&%T?`Qdb=SyjemvD9dcs23ARPURP@&GZf?9&_Hki_ z_IaCs31VTqGwgdcNwt~UaX_R3*S3YW$-*u2*^u~QD@%L^4ycozn56_cN_fq3`cmH) z5)cL*PSQ-PPUte1fml_aCchWLwX&x@k(-NmXthlS2L&)GDVyymw2%)A&Bx2_Va6rp zm)xaa>Pafi51e#@pDmtiapr8wPkZFiUG!dOJ`M3oI4{LU!1neLftMtKmeDaTmP1Pu zN5*~P@xnLoN0wjt8v1LU0H4AkQb@?%-_<-9wb}9xmuZ0*IP+%$lRIP%)$Iso7IPZ& znc`8$2OJdAM=GzJHuBESTyXFizyURt=t7e2mCd`(gm&z*on7QV7aQBXA686t#nrEV zvi(iFk_?|okBQ>vn2o`~o`YkwmH5-I&Ftij&MefAtUf1Inm!S>zuoS*; z>?KCztBu`V3I-m&Z4h%|GaGZY(n7ry69$r7>4yy#f}c_06rYz?U%fZ5X<UA4T(8)HgBD*LOFV3w^F4M0pOfE$yMjKYZB|Eb1ckSB&fgKY5z;bSL|rGC>s z40`40^?mSPAJMC&X(4vTK}E_OTXPY(o`Tlq!__BRz&RK>|HDVTfd`(O4Vv8<(FyYA zO5nu~TP=SKz<<&4<@z)SdO3gB?Sk{65An4LZIqy>PCDs^E}3?s`@kT`Huor%@P|129l~P8 zyE+~Ai&HiolV}aDy&|T67|xnDbix$HILH&~D}!rUb~E|Z!zyu0Pbj}$n)`jwrOPN zJa+KsjR9t%-A-{(na2Dez>4sf7R#F_#NUkdt_arx;8L)YH-%wDG4J{#X0M7!)i zfXK5TLi*m%Aky4am)-<&##|>4up)?-&(E9c6#gXfpbsHin;cwd)GEzW1NC*nSIUYW zs%94Dr&pe*RuFwcL@A$Nz1bvTacc0Nn#h8C*>6RG3VsH@2~rR8xEt%>NVpHGt(_KD ze@`qW7@D78yd3`mKt8@s9}{#VcoL4_EhM?=&2vi_ zP1=@IfMWjL-7}c?uKe<^E1SFXzG~ii&H}v^93E=l-Nicn=rp)r7K>LJC_HsfuoOQC z&FWFi!Gyf4$#iwp!dj6X$voFOK+PuYHUC;Y^iOv-YumzT`M-4S>okv$Aio>J$o0PJ zE30)-_~dM<_|(Cnz;E=G+`xayk}eryIt(_7ogMpxi2?KP&~T~(pOtX|V`ooV?dt@i zSo!?MjOZEU_`_GcN#wX)n-0r7?z{>?ek$xKP_&!xfH>pa`9SUFH1>%Xr7mw4%Et$?U zZfK&ytG9gObPDa;#zKp6C*Lvs2A(@I)$yJ&K9kl?{CKFl&F2J7#I((Uvur~xoRTXS z3>R>LO&JHJC0Ai^6VTlgIB}t?Nj#xu^)Z%Fr$oeAlc3RP!@{ftonN0FG~q&WvK1P@ znu+ZF#_nv*eTaZ28(qq^#EW?MYHJ+ItEG^xu}6sHWDm!EAbPR#K(Tq4yU?LBE#WBXu9*@9o6zh71IZsmf4+T?roKHWp{M5x2bo>?EXEVH0^OaJ3uSCu}xywvDTr#I=9@Ros1kE ztEmu_A2rZ=$ll(6jCu+)r*S2>(W-aSnZa744hkFKFxpjIY?I+u6WgHrHpRM8(K!2; zwxOi(?Up0kNZAkKqu|0RFZw>~OESt>$4}jnGc+>&ym~vdA?JrXtk2nM=JZ5?GtURk z$|MGCHCX;$3mqAVV_h%Lbo*)U$;?-N?lz}9x0YCCKCTV3`nM5nbN-(#i=hvCU=5$^ zq5~Qe6)PO`SZNn``b5zfAJ+Q7?M^;0ij&xkCAAF;eH*YF$XiRyIOg;vDSxk-4sD2= zuhNE1#B|xjrEWThWzvOhx~!K!XU*5q*Rv)c78HNO3h&G-WWE|-@Rj~5kj?1;^CR}P zHsHM}tX2c)Eh6jrJeR@*%-ZJJa&lKK_V)Pm&K#c|-E({~V}mFqwQdDaE}!!dATh2r zBlp@aHJ;?jvsKN;O>-+~(!Z`R}TU`5aVdtk_rmSpzmJbA~+jE!6F=5?bf6i(H^ zuFpiLTzsD;Z=XX%hnqJ#5$ zE0*r)vmYlN%)lP1ul?J|jDwr6iU%`14ccvT;TzmN#KI^>KdHqtSF6%jl9T_JY4A@A z;aD1Vd5b^Nn;#24cIX}@@fIR#3B(<4klv3EcJ3UI6S9463vq&glXFCsHyLn@m3S(f z#sqz0oyG)t0++@FenOYV1bQN!CJ%jLnI;c;f|Mo?e!`R{4|*bx6ASLl871N00Xk?5 z*Z|u_C9w-Q{ubBV>jX25Z?uZISM}{2?B-`02e>=0?3et3mMj3ez~<;k3<9_*<7#@d zFov;>{vjM`KmEOM2O04dW8V2wBod{9312)bRj5|7f8gmN2Uvid(0+rXxud{=#PC3i zGV|{M8FU2bgYDvyFb630m>G=D6u`94qI+T_zg#8!^YG@!FPqaZ!|<9ZHaFhQe4kK? zlUUec_Chp=DnT{Qc%P`V&?^NkxA`v<>hMsc7h6yIN{m&$Ejr%-?+4&E@p^w1ukzI; z*Xi^3K;-IzMq`EX!r)-@vPIJ<0~nY>t1mFb`S-||x&O5ja>s#%B*oyaIJkz3ZDcq>@Y zbPR#8I8$Z|f4*9iHmf``Rr6!9^7-xM)Sn99`>MB5 z7w6n?bN|!)iRq~l;l=xb3KX>2h=bGTRp)YJ+l%894(xEM)Zv(J=b|$_D748ndgMIA zi<0uvS4h_yg`F&1@WJGUJg+yzQK59KU()v*Huwb8EA$8Zr1|-@5M7 zBLaL07^!S^Fhb32@_e%v9%H*UW{n8N=(yPtwZwWykYkS?Qp5%I@xm>HJ`|U!v*HXe zc%008)cGtp8QNTqx2HXjOP5Wb%{^ZuHa(dKt>Tfiw4|^~1@`S3bngrs*PuAAIo}z) zA+;UBbiH#V=8NQ>v5*{>=E}xiHsQ8dUJ%ZI-rxt@d4=W8q48T_)YlLNs?&e*`4!}L zN8X6Z#9C2MhF{4{?Qiv>VF~c%WOM^V8JpwgA(_#g&k=N4)*YwFE?RaSPfpMEpo&1B zK3)l|0c^- zVM*ijYJ7rhg3ht4+k{`3{&|6C%^}fjF6mh5`z_t67T4l=2)`ruQ)`Fbe5CIBY!9?(Q^C9?fbUN0XC$Z_zsi-Qs;U23c-LA8Xfie1h*@FKdkE%gk1~yVLya zOTQmIGG7trOy-Z-?&rd)dA(fULqOsx%pAi-;ivZP#^z4-1}czBkws$L86QnUBqa7f z-!Fj|6^X`WzS>o{St5A9iZ>$MZFnvjvRTr;n!xf>>-(FqptNk+O#1xbx9jfAScE`J zPj}C9S)F^k^gW#kf|_&PwxZzoB!;x}>ghq={4qD1nE_ zqU2jA_VooP7w4OhVSW!PXd!bAymZgvD5ao5A(utt3ei^O{+H$Xeldv?zFc{lb|^N%6n z-h|M6~R#!Lu|YA@xyR$*lfA89i^KT9H-?(C15INdOqD; zbdrZY+7ARA9O`de4$;_>K|A@2@i$)q7rpH0`_p>&i`MRMZodfLUnOqMz|po!Y|~Y7 zFfk>iq!>9j+<4%zZOxTaxstzlTh+cq6OI9kS4+!u2%V4X{JI(9T zet(GA^=k>&ys{Q59uqTlMh38vc~>9Cf#@$Sx-95aMJ#5M5CWfXRI{@`R-Lm}weekn z?8%%PEm^rfZH4)|dB;eO^mMb$bDl8GT0B#!HKN9?&~I}CdDGaHz^c1B1A zsqn2u9cqU;Iozk` z@uI^PPDD}GW5~aMPpV^KW$xk%JURk=)pbedb!~i1t`$|BwTO2{u?fGMBlU+yswn^Dm1{;WnQ}YY% zcUB!@wBAuI$K4>1eDVD~Vg5UC70NRsNJ#`k9uAn!T^xmSSO%eR{KM4Ar9y^cB} zmj016LhY9kBrszHMHYZ4S>Q(jJD!#W(!=O0wLg~Jinw;ptt+C}Irr*sf~((UgQFrL z&iV4;zhc4njrS`ZXnTXRxz61n*e*?1syNxgwR$Jq)s+L+la`^zYFLR^sq{>+!s}>lh=-BRq=qV)fR4T^pn%I{ezBbee3sXj^GHn~AC8 zQ)Bk0_mm^Ni{oOMIRei}bH{r4hV4`1*a*|F*eL#|2Nxfpdf%2nul2Itg(u+BJ9S~N zz1RxgdueZeOZCgl)z(l^)NoY)2NL#gYwrJgV4}DkogiULaeSD0#~0De5ORkwjjGLm z)~}i`?EOEZjy~Drn8d}gi|+{Xrf;o-a?DP-0sgy$5IGy{6~j)WXBgfcvVTq0O(Mf~ zz(ndidqdMQD@g)O5@_G&{i*Ee2M{TrXoop^U9*Lc*q|pZ{lS}`?6(d; zbQ{4)50|~CXxY-zWQ;B=0Va_;^qw^^6nhMzmte`hK)0A?Yg8J8N)-{}|7{VDAyDg} zAS&urJow`BK;>GZNGHONYMun*c7noQ8UJ<|isRFJa>s%A;8NGjhxxz->@7xBr^5G6 zdu#KUE=Z*Ak-4QLw{2=yyNqJ_2egA-<(dz5NFM_6Zg_$IOf$h8GtOw4j-ST*-UNeg z8R#=*dC~EmFp|H(zW{~{(QgniTyx^H1(pp`)fcSXHUW`;@J~?#lb{^YW3;u08R$!t zhVrd>*;Tzv02U?cRnaM^F+^IML&2t<4_ww7u9eDvgr97%WD5xyJsL)1qg0u{{IIrN zM>Tnumqk8_O)~8BUPUB;9#!6LSIvyKBws6#hb(m_A(Cm+3~qOP<({VQV=>-{zl;5D znW?-72wA~o&fOMD1Q$*DGER-!ydNrOdPqgw>8=&e`U(S{P#6_{95Z5nfmm-W1j~W~ ztto6XEZfam-;nGJqEpIW{`&2lh?CGN=6mJDzc3E@x)8+EbkKv$J*l+YnZ<|c?yLK} z|CCGZb+UXhBslrorXh?^Yb;3S?2B(Xr#JJ3GDGefQWxaDe$3m=S(J{o7fKC6pY%SZE6&KwxGKQ5E{n;r7s zx^_dzyi__hnzP@ufXesJnLXGPW6`(Yl!$3J2XbNqrdj<)g`WBO0->QlGlBjV{NOME zEYXciXZ|Tfw=g)pvP*EnL^_*qQ%G)M$O$z=wB}yKM`>rU1U-R)9}{+{=pJ7Yvd;oP zc|Hm5*h1x`zQ*@ZouscFUUtkrGM%vbbRj#*Mj*`a<$N$>QR<#F+IGnShD7mrv34tJ z$fI#)SpC*L;f`uLv36OfNW>)`LGp~DVPJZmrhI?2yt_;vdl8rQBB-c*?|frfy`2OX zy7JC5F39FUoTINHxxc&w9tWae%>OEoTS%}=mz=yp2Kx1QD9keUZltK?(FtUQ|qvbC&i zWa{B7y7A|D8d5(91efKtYexIv7`J*=U!%S8oYT(xqteL=Fnp6?#Uw3Q(G!DWtH!b`_r4`VlreAf)-_EjwO?^R~Nar3GTD>(_A+AcIx~B})8k z(@W&u4|G1q8OuQmwk?v$QlsO+_--XCw=P8jT0ktC#b>D8@PA)tm=TavaNlr(q5^Q~dtn6I|nN+)9QNGBv=Q%qY zdPj}yIu)}5xqOhxsvpK z{L^o95RRajgWle$TE?$BCQN;lDJvN&0n@?8*VjvGi{JtV@qqe(l*++KF$=hyf-VRqsnrlz*GuKFHpxhc?U zT8L5^vgW1EvcBSE3M{44y%{0*bG$Up?|w)wE6KW2F0XD8=TY6XEI5o z{NRxweq{nEDqu&TkA~yY2ke z!5z;CO~$2+)-(7%V}cD?%gh`!OIP>^YQfIJ*~J&UBaLovbYb@AaX>TmM511D>?41qdu^kVQ}Abt?)$?p|5Ea^DXjkfuBFTG|5{LqIZcf~w4p_)^_()2JbGw}_ zljB*uLP0l8PQhaKIaoRVDw=Lv6KuJ&AJf~S;#Y$0eTrFlv_wD7xjJ1=F}&IHv<;h; z7=O}Un>CVWx`B@UYa|^UMGUk`TsQ@jKpvZ(bMI>PbMbmNLu9yaCtH4d>PIb4%ZY!@ zaw@AowQcsEdIin5_r>714vsNSIe8_FMY99W7bXs}p60b_Zd`8~nsU=cZ-bkegmeA1 z7;U|KTBaJ~EiuRP%JRcRVwboSw0tTzOM(A}G~Zi^>9{f03SUU&;&S!axxb8r6F(Ig zVprcO2RPFOsQ+QKCu0fc9zioJv5_Y85A8)@>qVIQcISW>aW$$|rU~yyjL^u-A=|@6 z0DxVfNnYIGW~P$JAUt$25#RpV*Wfb;6k^&ifWGvp2)1b%dExy0GLg#dW+1k67$UmL ztLrr>KA<^mE5M02@I5&OoSmgtEOqCI3jM83wEr-$QWhqKAA5|OPK5T^;pbPsItN%t zH$k9yM($4Qma4sZ+W^EE;Z=<_BM<1oLv!PD$tt0ZYZL4P$LM&tBg?XZ5XL(8r&N4M zE90C0KU|tu98$Yz!%fg~)+aBUVSk|k1xL1F9Gdx+Jb+cEu5N5gw!sg?g zm99N*7I>D0N;WrEHa%AMjYsn5&!kA8Gg)X=RcPHP*w`4QD8wK{s}LFj94%F_W4{Sq z7MMFnzO&6y@iX+3w~BjaN1o3Zj=qA@)n_90sw%m|g*v~+5LftgV`ouUU0rRs9?nHU zcW$I~==67HYMAL7S3u-x(C7Jh)ls8-&WL@cMyHISMfzX%_Pf0m2ouQaaEsT#R@TCG z$JcQt|JXacF21}>Vn%&G!QmryLM*1Im$&o1xLpx)y)wsu z3YT2&_)1&v%nojsw*(l)+s-J_Ytk+lu?XDN+>kAf-7z+eKSVy`EwJ#IJcQ$fgXF1X zPd7MJE4Jc*lZJAQ!_l7F-Gc#8fSSZ(WNZ{ZKW7}4xGyjGL+giB}gLSLI`Gr9|S>-JjlQcU%)B*EH=~3tpQrZp*K5E-LCmouL_sIrN z!m;|XK#i{X(Ca3ubOnpfcTDs%GLcvvv-VMuQM@x~W#>#8y#ZSS&0~?JA;0P|p!Ci` zJ35Ty*R1YvX2%w7c0fO^E559!?6g@31%8L2nB)|QFvG zqI8uaJ6#%jpUEne>-%*QQXRb({`EZx^UP|n^X6v8y5FY!=bgq2c{rmRa%`Ku3t6@H z!p31EUHClbayq!tb_Tm965|YWMYGPg*m4Z)^^k*kB&sJ-f;Qqv9h2~t&$X_dHr8#K(@s~q|DNewF7dG7aJB@zui zZthE2ZX%ipJPMVg87RiBvdICZ>?IRLDdOZ9gHeh;`R-1UZ#mGvYs|AMhUK#kzT5{1 zDTT&LWY-x3HkMCBu4@rzefmCO8QBKgY}lM>%HA6X9Kk!>RM+;0~RE_z;(+AI(= zR+DrHwIc0MlJR;39yTETRlt+#UWD3ot1R4N}sr$nt!f1-F{ zaqamh+t4gsyriCRAN@(l)+Y3pLJ+RW`AVYm^6RH1kVTQ7ie)cy%z4d^7=(vDs`_mx z-TpS>{NTNu=&+$n&~)&jKFl7WVUoxc5+3>t+?uPlah_i{RU@|Q<)Gv2l94+&ocKyQ z18n8Vd>_mW8B-27xMclyj~82ZnUS2asyFDcprKrEj2bZ)Ldy*=H5b4c8bOc5wky?m+z+LdT^yEiH;t1ss_uQkvrPn$O| z=+wf16wdq&s-44oCt^QOcLC0KLbHr84)5$TAFx~|U$!x&phNh}Ft-p@sY^)Ld|0nO z=!7y-I4(iSiVK2)3%A_NcIPjgv?0Ne(0s#a2aa;e2vX6T5qmz^Sv`)7(M7B@Ir8T+ z=fuu!lk_l`d1eXw7$J?RdyT~1&Abyh?eCw%`sk)&qYxnz}^DG!lli1d&}vH zD?4xsdt>!po3s1UcTu2M#BxDBb;B??fh~7Pr06fZf>vJG$bC zxqf8y%V+9+!epAw7L+hx#uD-+uF8jUtoKh(aRoH<^Xf`w<)$A2*sw4#KTm?-kCM|O zx(z@Ardt~)?Ai=55i`0-wHj{Ox8XROy!S@LRhe`n$g3;K2_9?SIbF5f+xG})!eetP zc&cmCJ`43y)0wawPHkNHE!mse;4F>!oYAyOFy`Lgv|2=JFs&Y1U}HD0-p;;PwR)o| zu*bN16Bg1TrIVdd>r&dpSDk;cs9w5+(Wb4F{X73+T75~NfObr^;~nYs!=2n?V1Eq& z+hj?^{YD>n62W`VmyJY02gfSo`n}Z9xYVL}%&$$FJ{xI%LHk%W?_yfI6vVkyoeyaq zsC|tS*K9sO4%jG4Mgny&RR4z?U)zE;uGy+s4$zgCtPc8|pe_%)XQgd{I;_z+%MizG zRg9{rFg+Sh=9R-Hd+l6xXO8-dS$D-))Z0z!KOzyn5bDs|{Nz8?5@Kti^X5ALv&k$_ z#l{O6UOt)8^4IVG8`hfTo_Xl*R!R~P~U3@lu2Tl$L( z@I%0?_oE1!^>GSRR*3_Kidx_Vp43PxT0%kmJ++>5U3$=n=hP#=8p=k2O?A>9Fw@^{ z-qT<=ZLxl}cz{fQr>zV?R)^n9xvat!ZYj!JbT1A-8s~Rm_r8_4D3^g>ql{IBm z6M^hSuin6*_*M4Y*jxb@FBVdU{9>epf^~F4$2xFnn2~pO*NUo4*S215rx(lp36Rpb z(Yrp{^j1B58++ne?7&`*sna+R9UMtHn)Ga}Ru&6a6WZa+T5vAfks~5V=;4tF19}5V z$-tnHf=Y9t63gYNM+H}#cyx3at;+tORCnR|Jnuiba&ExXbjgUj04PWJH z9jVfT-yI?bD*qA@Z~COHhJ&-i>YNa~{R|dsa)O&4F_U7_0uu@80Y~oXNABm`Y-yZF z;x^&^>l|8h$a!mb!TmkE!Eb!z)FC|FmQl|I_bdCFNkv7`t!j@Q>JoP+2h{^?lt@+cmIii{u}=I z5Cnt0*OK5#+OiN1oPmI3d)~+~h_Y^W<&XPU?CnXMdF2YBXK>6CH)LZ}aTDqP@)Ff+ zY19CNyR#Pud@8{2?$^&#m)>QW*iV<+Ip6*B&;xd_uG2#uH{VxZ)kKy5f?PeKoK|&8 zF*E%vVnd+GDEgeqw3SHhZ(SI2J7axzRwR{byFe-C;pV-5z0rFHc2yDt>bVAyGm z?8$ANnC%sRzvqHv2G3~JnIb=pV#v{g7lNDHgoWrKfiGg3CzmGS1_g}@=y`M;hf(*p zI)qM8CU-bZcjn@oE@1Z|O1tJ|(irPMjoLHMUfwa4mW4~tcag7{CIwA&K#(|}d7p{_ zf&K}8L=pThPL1oXO{8bH*E)B~YkM<#5hJ!1A(nTy2-w4WTu8Cd+Y@iYNvW|~78eHq zwnZy=2=@LOqJ}vTbQhAw&gT;!9O*afJx1rWX)mK*5LLmuN+R%s4l=aaIRVOzC8*=5osp}t2-CfoEId#Y{T~KwUcTOp^tQjv*Gxf)y*ZgEbN}tDTe>hKB*cd3mTjP(`hKO_8gej^puO%dURThuU$kt!Y=5hUnmn zWQqFhshVRL5%1VnT7uNbKRGvk-s0ZRBf7F22!6B(_qFVGjGRMj->oj4Z>)6LvU}Qf zfS4tX=Ys_Q(Zol;L5OY|d2}`8=n7Cv3xY{{9^-JJW|qex@k~sTF{@35{Z~$t9|V zZ)I%lpA+}#<1b)r`GtxLRrI@EAh1n?sJI49gu@}we0LDDp!KG!Y0|I zK(|H*sK=T3-}j%wGNl}8Jrv(!Mos62?{G_vyE&AxpRj5QW%svr-|$S#k9D6Sn9d@tsRr zE9|9RGb^$IJq?XM5Lid)>_FM*NmfdiSWGDl&;#TqBlk)y@0V*$hPA11QW?=q#<*SA zH%~cL20>l!44p|fm5Rr8H~%3kxM~yZe1$Z7qIj;W&XltOv}W)(-NmacJCT;XWrs}1H^em@bkN(hRbjA>4K_n zW6AC0A_(@3CT+={p%k#$_8ao}SF;un8%CXX>f~#Lp0u>QNFUFx6s*gx|?zWi0&?q~Aiwj#|RIURvLx76+! z!-dU9qawdHUGis4gi~Q66EQA4Zp0A4Ukkz!2JDMa*8KZby`I>et(s77$Mr~HHj4AP zJ+~Ku(ae@|7<{Y{F;Dc2P^F96&}BOH!GAKDZix`(7_8xLbuiYyt8?%3kcaq}n-}!k z?_uUg$6oj_Q;%HrJPdw=2H&n$@0qvTJngF3CMEK}hMArR^IsN}>+$vU3pmG+sD3tL z$0TNYXh}W&Ga|=i{>Ht8>|=Z2{!+*J0SyMB#|23d-qHOTnHO2&K|Gx|I+*JkOfw;` zhm`IO3N4>2wk>F2^)#82d}^PEl&SlaOH?_6WS(CI37zomvs`>LUBhm#=eVB6-AJIx zdqn`}h3#=V!ml0qCZ)J&6amGz&&|cb=60=>&T6>xIybuOR?wJYB=XQBb>tkz;*Gmg z+5F+B#)lp3cC)U~=Q!ov<~_f%BuU!_?8Z`v`kO_1%NG%7#-^;!>yTQ|rU&I}5KcpF z{NL_p8lrkug^cy7S~$p(LGKwxPY9oAZg&F=w+meJ-GUAU5hm0e{MnY;$8!JFB{=i_ z#7irJSz-_~Ey|b4xiqNV1FrrKSS1nyIJd6SIk>`p`DUEA1UQd_=kd&euuNs#!#PYIN7h@P< zyaR<&*~weZ+HgMNq*=+#-}3MZ7!2o+@JhGls9(h4Hh#qA#R&Y<8~iWKmJY}ipd9ov zsQYCB-LQ~YA)e_Vy2#viYwY=-oe+@3nrKEKtYmHtyU?tIW44k+7{Y}dBL@aZ+E>Se z8lRw0K5e^yOBHcr4pRtSW(y;RD1bh|br~MxuKZ*1{mcL{1r9}l2EN6lthudMcM#iDiUX)y@eMl4xB#|jWeblmgF)~;egtpP@g*cf- z`@APmxKy8kt=k7wtozdNez^IR{+rbNoiLq1rSf0B>uZ^(0 zbwMK?v^G9&4;jr%<0bU#TXM>_`$zny`r(Q*eL4<7NqkTM8qD`(B4~n}qXWeOt*YAJ zw-DzE(_o~i0&jsWWU^TZrp9o z`@Ky&CdQ;M-SpK%DvhW}a`)ouvRlxQfVw97gUYtEFk`H&@ zAwV6++tYu?+;_X{JwG;Yi>*Hg{LpBUAVh>=E8`qo%KM(vWmsW`*@NrA1KJ-Ik8QI1 z$M?GeZGTJZJn*&cswh0R)Zsc1OAL4k^~qjTyy&?%-U9JsZDwc^C~3{xyaFCSx%5wKuUE3Czw#r zYKF?c7sf}!)P-obVilsub`Hwwh2>;gA}IyE>Y;LE5|u(?sJ!X~h~q5Bq>2x-q{?lK zH_SB~*W-iW@-vyhrv(w7g+HT+`L7D2Pm0u|l4%%)4G+2XsZg=QWa(M;LmkD7edm{@wymvizcgqPXdpivKGXpad-{z>__)E>QfpYfk7$ z9Eg~P;vZWWq_t}$6S?fL`lGb;77$#B^uNc`r5aMZx<&L^0Tb0?(*RL(%skRqm^+uS z^5Iez2?*}6=|5~B2!kY9$>n;Eg!lfYuYC}dNJ}L<7SO*hh^H=bN)9l;c3G@ZKA-g8|7I`!F*dv ziN|PHV){|<$^^L2YF9W)%nPXRZ0{g~9~}2bVm89sP~J74L2^z0k{LX7kEUcqZ@8bc za^{gu#v+lMTkwDO4$<^xam?M?ctIJMkdIz`judk|NmuOqfiCq)urz>Q>__@lKX*G?62MfB$re3wd%E2{Jf=Cn!ds{Dp>|*I!oL52%>Xy0_-x4C zn>kFhjr+4EX8FLg+^IDdzs2?PV_U3xo0mx#C7aJ$nF}|!0jAP{(b)N6IGjUoNT1Zi}G7J*mY^gpgXMJT%>uQp>^#}L;e0`SL z((T#%nTJCe3CXM{7%c2Dzd}w|`|T?^N)(1j25k3xm;O}bA5)i2-rVO&*Pmu)tH0$_ z!R3%qXDFzNy5j=H=TT3uS-ib;R@`rIvut}o!Y2MYGlm8k+6xi&k9{%N%Y!iL{aA!i z_ui#w8_v5LHM#j=<@WZxi;Lb3E3&XW{VmBr@98VRa0KC9g5M#ock7r+IzqGtWxn&Q zh=x9QBr~F&FQIx2Ajbr4+y9qnV_{7tARI9a&l=6wKJDfWBZlvj^1u-cEx7R6kJ*4b zHHxv3O~9(OQL-1rq3bZS&jsH{jg$%HWS8K@-=@^&xH+4%SzYEw(+2{akGg}hPWC`( zn&@@)`44f?Rk|4#Sqzf;(6$v%isYS2w!Yu1+fp@iLwP8@z12oqNMJC1mEmV})GIVW z7{Lidc+5-ZlP_4MgbLq8>t?PU&EWqW8bCtT!MG~Ms5nR1`dq7yo z1XOh})G~7Uv|t+J$4k*zQq{Ubw1T0{e$>oNJ}*gJJ2Ahu{4r;i>4loPW@2AQib9F9`gSh`KGNV&~FMRu7#Ne7vruVaT1{j zVLb$u+Y=Q0LB35kzrJ+Bb$~2qLICMs<%DoNNkQZi_`7z(MNn{y<7?-HQ%K|lFfCB0 zqd>5hdv^rM^*}IEA$o{RcS2yP1UbU#Z$UQz0C;W@Au!}Z++i&%ePr!Bm|c4Ee4lW> z-RFiKRlc9L^n8k5TLHb9UUf{w|w&sDtW8!)1zWwX@lZ_xKN!yXN3oufEs zRVo4fc}IV>&=mWR>U^NfkI5`DGuz9&dap<4uDEet>*vquFOjhy3RmWWgl0d>QhuOc z_$>z4Z@Es~KDXMkz*2qrrKgOrr66SQoln2$>9+<4zd5UBbi25EkK@HmyL$1yY~Kb& zLqu7JNJyP;lP;KF9C@6{^Xq#FO{sl%=+n&RqlOIKCb)N5~dQnfS=|R9ed4QYx|nvxiSykbQVk|m(o&g@uxra4^=+$ zsW;}kQ>W5-6B+i}k5vv6*6aJxM`V_%ApKbm@0*ld`f($&nqM1Jl3t?xbiQiHFB4}zWtFU|CEJ8wrGv_}B9E)QI83b5JwSu99|W|s{- zEsu+Nt@i}2ngNwQ5#~w-dVDyj9|&N>x8sBVXbXH~H{&Q!=h5{>?q+i`?8QF=YT*T? z=Yfmb$7)!Df4CvKIVK^hVJ?uXb{?!(2%Lr_AhhofH;As`57@$`S%h4(z5$&avSoo! z`%x~|$hY2WfON@^^Z4!?ce2*{bfXmT610n4Ksz#3G{{TK&l;sZ{TmNt0e)eNcLuC_ z$4CTjIaCsQgg{DbfPp993jHbfLD4;-r^Y3H`!r(V(~$XR^ShCN{+ zR7vNtN|89ld{g5UMe2c{YZ7T57l?#7YcHA_X}$mi+tSoiAMH?IFFf0l(4R+@8`_Py zw+U%-+UIr|r`Z_WQjEl3K#}Ee6z|XT&>F4AZ-cCU9ie5@(Nf&bV3z4} zKx&F#N8)=5n6j&z(G}iin+n8IkHkq=E9+A+=Vb-MoC&{7S6Ipmylh_7)lq2e#OrT~ zE?~+8f>_DCd=c5EBKhc|UiR-S0YzZXE2tYJNl@tQi0d|tUbX>P`JSVz=;X#I3ngm_ zMF`FoAMWW&9;fdIUzeICM7Q=8#jn-MLD^@KeB21n5v1DaX-fCP>&k15632Bk zk)mfGsQNGTo(Om&zxMUTv5)k7OckV^;svARhSN@s7W}7(I!Y6>Fw|9^m+DSIRO54n z+zI3+T5Mn#yy7MG*3D-Mofx!>e$Z`&Iy>-EVZ;q*5=fld-dh5!aFo*p_Q;AgeJaXz zk~WGDkQH#`ABePu@H1t`(@QzS$!A`alyGWB;#RTAb=P9jPRMmPr2*UKd)tZ<4%R5& z9k7K+c-?DqTVFwZRiP_fO4kuazN4envy5YzY?|;-+v%W--IbEO!K5?v1TPYU8_Gos z#aAf-lgF5ow$`u|IjkWeC}D_|2#D0SKWF$xzWX{7J6rZL+In5YYra0&^;{JZgl#Pl z{jy~DbAZ6!F)KkGZFi^CFD_?WS~po~lBcy#?zlYPs`8*2+j`Nuo3AtHl(#L(X-he2g#k~Y7m4|t_J?Tea1J*o4R zDr&ZL$H02+K%O4<(Zw92dH2>(7LzU7bJcB^#zq(LYO{%3$B#fhGOP9w(9lOpH*j2P z0K=KZhv|=77D$w}t?m(YvxW+!@9`%!oQ@*u?kV+tjG@Qs8<>7U^Xs0amVtL{K4C;K z<9gytFZ`Zpk$lt`66&zEG}RVwrqR=6G2Uq#)A#Lr5Y}8l*8Kapi zehD&BI)gTFCJ?SRnX@H&{DENzFj8510eq9q*}OXG>V&SzFDT(IJnm0snO8y+_%5ec zN%wU112HXh{B|JMRrwKC)1qZ{OINw#|y*R1){ zTTT3U7o6}b5^Dh({qe$To|y>WWC{ADJU>Q@(Pr9=@^%6#W%50#8!}$RLK+gGe3(b7 z%ak{+-_fIww)iaay28tcD{3Te1uN3u-q&Fe;}0J*bik!M9qHp8G^)Xu7RR6^+-Jlf zlYD-t<4{Z6EB4bN>UV9VtEA^K$cAL6erIAx7^?6A)fqv1q^lXqL=!({2%XRoU7X^F z@p45gN}-TBADeiHTv`s}WiRKQUola=q-nTj4$@-AIlhgaG{;Gh54msIY6!GP1*OZ| zJG;=w<_O>m6;vb|DuK#||OE%gFiNBTrd{w!~mF!~~i`}*LED~|9H zN8UW4FXew=Cwd7)n#U0yx6jWYuR-9q`shi|*7FZoK)e2PDb$!H#=~a@qTQ52Qa}xm zPEvFZPg(O}RQwt+u!}vWggR?0Lv88N>!Rq{X9a!ihNnb<*M;O}$yxq5RiFJWe(`0U zWR;AdZ~)dElL&;DFOau5D|B-hGObO)Mk_~AldSLY?K9MS$Ss*1ceX?oqI zrhOWLPIswXAL2L5W^TGlyLL^yU}LPO3o1^?cD~j$vGq!Sca+I|^kna4B~|_>yf!e3 z+YtnEJ8^z(gVSkQA?7m&dFAEV3iE95nEy?GRRa-b7M~;fqzfN zjq{|lRNOZzvc-ErfyNRq*or>MDjmU3CFOU7g=w-y%Ys1SN&@gX23~~knr46x^0?St z=!NTTEYF|fX;yDu78@_NchtNde((7!aP;c{e$Ro2sO%J`n)3Z1XXDHzWZCM&R#H>n z=73vj;T=m7LIrclYj49phQOP$Q{3z?wMOlKjDi9^GmG_y6wqp8xY|Y9Q7u7Vs?LgH zlK&8{9h`@+iBhXq`|p2OoP{CNR2rz&|Jixg1gm(^am_oy{HI-VNv3R(XHN(0V+h&` z1`k9?g;ugxHXO6RzUJr=>+$`)z5MsJYW@HqZ)b5o@`QY{7G5`1j+A0?&)~S2oGb2y z6E0hhOxqflQ&26GdYz!n=w}7yp`(QgNlIUSgfDE08t3RY6O+#rNYd_@u|FjR;v)R~ zM9I%a1uo>KIIjT3@wHJdJ|RhUd-uifv`uG>O&xU&D;tN1og=7Ss=*^W_m3B3<0Mkh zsN3FNP|P$e3J$~VqGipxq4~oJVxDO6mz2EJf>Jj`A=qdc2X9K>asNbIg6hvfUtRd% z>0ikXw&1Of@Y)k!dy!s24pBnis7U#N%@de0%+}y3HcS{HD^q~|PnI`UfhsVCQbOxi z6fKSHeS7|S!crszh$c_qV#xAKb9%AHt1SeZc1L=tx zr9Ni=^+(1MZ)nDbUz$i6bhf^#;)T3JHRdnDNUJg~&O}A)0RQwj<9&`F(6G^ir*1*O zoM@`y;kEYSj4|I5Q6Pg1j8rvKgbn?KDMvz^1&LZJ{l9iJvEyUSchk3bX$@kIW*p0L zGS*QKR$%X@W)&B|rIZKS+*yN$hhh1)V`b+Y%2*MzXMdk`q?$6&nF)_G+n5=Mj2Z`# z)xz>9kSQxky=FH}3kQ zOL9qxMEIQ?8aL}8#wrv^STz*3YGf$~pkE4=U~*ze+jMe2oj~nMlCbg%`G}i*xvb~g z=Kd{fik;YI4|GhfPEdlzgb2B$GR<=Y?mfE2yd?)@@xsJyP8{oyH+gy!8Q2I^`2V`x zSRuFi7#-p|kBqu=yy*199t^f@#1pNxN2}B*O(r^zXs#7iBlC2OZ3!`sbbj=O*P>hU z+b%ck@oo4I8~NlbJ`(Aft9EAO-xl@1%qt5+?Rua4%5&%zh%#g-xIxA}r4%6cT99~e zX!`d*1Ue#hUqG2ZQCnlvXI7l3liDyu8r=jYciT>@&a&u-4p#)N21q(`aJ(qi>OKwZ zrfU!nWsWxL?aR0m1e>pfuQW*x@>TMVvg{d%P22c*h)Ai)pA~$f?(YE0_gpEa*M3Y^ zksX}=yL2{Iqo(RdFn7Fcyxo@M@@zpea4aOgaM$Syf>5I>DtQNIW*Cl}?&--Gi({i& zWr{Pc7 zOZ3hAJ6TA5B9@@~Xmddr4D}C%7*_=`iLM9v=&|Hn0>A)mw_w`DcI=+>Sh#wV$={6| zyn|*9;j6L06@q_8`z zEGqtczrf7q$wFaPQ^JclO_Ci)Vz?5y_=Yqig+RHCu{cxKvb!PXr|5{Vo^&I?$q1#W~gqOzcY zEVjC@CqsdH#7TTgxeCNFhx3b^zY25Y(qzj{;5kU>M`o7pO0i&8W6ygqRe56^Bt+lu zRxynEDjqKL-9^!fwjI??U@Pg+KKm@GtZG}ihT!7%8_lKUhoMIvAW!?#Oomv713q3X zI9-l64bc`okp}iQ(~F~{>57-6@Fo7ZTPs8cKIFEisq3U3jkGjz8933QkP*- z3?YkE8cX>BD7@uGZkb3ukngs(8ph(3N0F;Qbps(w`&@4Jksy|^L2aZ8hPaZ zfIe&p8gWs+EaAHUdkvqZm5#6_qR-D!2=?&TU&SDucOEo|NrFw&<&2r*h{NXKDx;2) zsbILRv~`lFvQYSqtU}V6sJL2%`=-n4g%my5uvH{E-r?~j&JexoyzY!ibXuGdZ#+R6 z*kHp$xu^`5VbW6L4~(3PBQddtBy4!Db$uT^IZTdRG&0SrTd3Q8r0pKo{FLNX#GTL0 znU@T)zC=9jy?kARjmj`rpEeL2lOJFSwDeQg`DQ0!8q+`Z#JDpZV_DyH-+9?-dLwf> z-C3w>i@t`O29>K;b(7bMIap~+!zS6Ne09P?5^&jkSp50+8< z2lhz|YfU}<_hD^x^#A^iYJxVv_st9!y~W*Xew#c->&VSLrsQupVQ1XHU~<{{1-klIkhnjJCgoc#5mnLp47j`K2D^Za_)QV%V9QL~oU zGriBi?}Fq$>6a~XA7u&MxuTu@Fz?-Yy}qmy?r`>^7Bf|G5FIUD~zXCvVHgfE_({2QeHhOB4wi9@}=S{?dGLKRnb+~fUcD@{;L^ftU^TY5p8 zyyR<{{|D0kH^99#dK7HG`STQ?E9I_=e5&sXt5;XMWnE}`%bTd&Yruy4`0UBnaRhJb zMuULp=fh*m8_$$ddVcZ{jXzg(#zVm;%*pj`5(#_Jo>10pQ^_b$oMJDR@jX4ZMC-kZ9VLV69 z#K^RPoc0~S_w~eO#lvMwiOD{2j}J8h^58dBUS>$P5(!$>}o@9`qkGDtom;0)$VYM zS1YREuUia1@59@L1#hF(C_eI=V_d0GEaw|p?C}J1KolQIs_h|Mj`O1mt(Ml@vA#Ud zehBpCm*+vMJ?DAJJl9iBa-}DLZVd1NW$8RcX12hX^^_!ND1!7F|Cu?Ij~B0~mR3i- zOTgeK<>tk^dV1@7-Nv(iKL0S9?(MRk+iLJtUt}MU_EYv0HQ{vI_fB&7^6Vir@AX;7 zm!y+#)55%#=UBUO<4c3Eu>Fnu5;cmxr}XoSDO}EYr5eHgO<2QseSyR5WFz})(`axP z65;U9xqR2bLbS@N(&xdz^b3H!P!V^|La9W*K*`~y@?-Jqg(Gml7zonvlR=<$MbzBARIbh)Z?j zi!|HckMC3hZ)`)SZns;Yd-N>!@N#7Gl!h=MQDolpF>xG|jTMgt(R97`S@C{Ar$q18 zxi^vi4ZE;ZDptm?;m z4`$&GAQeMx5Np2iZi$|2UlIRxpQ|H>9VEM5+2d9&gr;iLAHaY0(v~+;@1JK5cynI) zG1FY{KNv(7+WmCGfW_`gY|`tOTdM)cqchhVy|(L!DJJO1;A-z{vVXQek@a~Jf?&{z z1|zNt%+Qqg5TkAjK^!;+s>f6IjYv@sDI{bsQOw7k#%SLTCgFC&n3?zg}c9v7$5ni79PB`qc2xbNGPeu;}ZQIB7V=>NJzn>)Tkv8A>2W# z=+l%5{7$2>GapB5Pj(v3tm!(#CZF~!n`h*yT_o5m!pT2w($ObHoRi9S#xLgo{KjM< zvZnLy_a7ySaNfzjhMo^KaYcMfCY%u1%JN_VoPmEmubyktRd!#Rwk!KW9*5Ab_M zG?wsP@nZ?5D6Tu-J9JE)u3{Nx=CCi615=dmCeEWXe9ks&7-~qZHlW16uF>{>%74u| z!`TCQe-3tp+4S$XtGr0jwuZWiMhDl{lh3v=;_HZ(9v$t52uKde!ieZEULBrk+>iU; z&1#m_Ise^z<`fSwsvoE;H^~6$s+xNl3&)Iw6V=lQfHEu2qN0=x66YHZ3ttn(r}#G) zz@LD%IM`(7gkp)4Isa7zJR3U)wd4R93XIG8hP4F&y}-V@;H{SX1xhzrzVh?d?{kGR@!ny`!%FUcrKl8|H#bzoR#~8MwJ_jE|#R z_1)Kf*=~eKx0}m(UJ?>bVW;{TdA6e*d02bjJdje=x_EFd*C#}{m)T$YGeXJ~`ih#5 zL3TJT;#?8VdMNZ#4YjH$0TJ#bPIsh-zZT#m0DD?vT2xM{Uxla<#Fh=)bTT@K@C)h%E+wX z`Ki?*uxm%VTjM3;0>05|1Ka3qjAJM}OT}}Na&m_iCl+B8>IpeHvW%Fdn6Spps~O#e zsoVbZ?7D&%X0Z9}mhT)cOH?lAJK!dM&C9sjD%H)U`nz*$+PLtLm6eqxB_#!xw$nPx zuB>tvNy6jRr~YkGNt!rd3{+F_61=r1|@H zCFup+*Yp`af{np`8)#I~YBpwm#~SRdhPu^r#2dFV)M@Sy+k@d>ed6=)O*c)Inc_^b z-|k6O5}Aj#*&16jnJNctPXGPZI)Exu$EU|>l(>t{aQ^o<4q2{Ce0Gdw#2%BK(BlFn zTTljFxbBbN!$aa-Pp5}&#%Q(vk>yLQ_e+1c>t1hk=shp>>;TgeA11TR^xn@rQEfgz zvC(cd><#J6|MXf|?)_i>v4kNr6=<;ZWME*8HV!%o`St4lp$3$&Hs)4RUms3PM|OLaS;h$ zM|1RT(VJ@CI-Wb;!IjQ)dUJ%Jw{|}g$3Sqb?h*bP*Q3$YjH#aA35&!Xwwv;GGWX^* z566>*+?46~C8zDhVSy31*_>GfEK=SffMKX6rV;n7j>z$b(O6F&9`E2_MraoIW9i-7 zk=e5kAobq%brRHn|bL@s$w7g?jI?F0Kp82>kshYN98|U)>ydLre`nEIoxJo@O}9| z6!^l9s<`EC-y$lK7`QGMRH&+8?-rNO-^`~iDQoVJ!f7aRcz>{YgXGozh`hHeY-0NT z29bPMn~iySyYIAmi{}R(Duhtq-EFw-3nS1<7X6c7t0|L$BXc92F{_1P+cdKml~-z^ zUT^520-|^J&)v`;8^6Q#sSumgOSI1x`}Ym4eNNw&+(GMY^Cw1(_61yFLOaCfBx|m% zw!C5#?bmvyn7$B&gD6M%I43mcMKA_SsXRcYNFS#eJW z7esbn-eif2dfs1al47cx`qJ(VG22@RJ6=a@j-Hvdz5qY>S&yUzLdS4i2{}vXqqfuq zGMk;Od3Yk0LR+R7+23E2=%SNu5lO1*ONRE9#)`tw$@lwOzgkQEfy6p5WEmV(J~z`@ z!(0lub~tJa25t3E0y*LO7S|R)swsu}0!zchBRu3=fOyF;kJIUyMXLf18{O z2=)qQkPQ5k$yG`AB!(|yMmH!3M&w<>@C3u=|FRa7ml;i1b3gr6apWj-R) zg%s^^vb&wWurHK9DgGEQM8cjti634ssJ#i%z?RI8IK_rW==nXu&7uQkUXgi(#YvPN zZQCzUz#aQ|oW*y{Fwf7{QD=E@MwL8f%y6NAb0bS$t$Fm%+|ORU=G?g` za#gk6KQi$L{pNk&X`bH+6yy8kcY#>`3%&WKj}GPd*R-Dzeg8f?7L1%_hbZlM1FKM2 z6G;vmLfoIDtb~S@B?K<{!?W^QW_mZH$qSzmf#csBtVm=Vm^QDg41>L3e*T2#(h$y9x@QNuS zW-fK%j={X5U983%*p|2OTYB2#YjaEZ+O1uKIk+u+FH$%>s0C6 zRHI~nRwU!y6OgV?7_aJnRUSiTEVG95s1BV%!_V#G;RS8j9V=SeCd{o7Uenj^5;%)1 zE+ZGRRX78XEK?T1EG#auM#Z?)=()I)o7M=ksQ*ejI85u1dDj$pm%y&7g&M9L>>@~4 z+QFmXlp>wadBIXH)jCeEld-mmN4Q#VxQXMl%K>vEv=}t%rihRUOb%z+);@&7$ve+p z%vauvdr^e@5EV}wy0ul-NcMxgeZiJc7pbg6z|QQjVo(s|#8g3r1YqqWW99Cjsuk$l zHlnM&b=ruNn>h1IqZOBBDXDqVI`kix(^}^;$|d8yISkI8oTjK$U|>A zNYhKrwgsk(tV+rE(pl}U7op4N=ERKG-Hk2V-uIi4b5}l#CxAkqyiZ2OctA4Q#gb)b zdvDr4kFJNC>1ao|Rr)F;JVGmK=^+xmg_K_tgHs_e64InK+ek#Kx!tZ~Z!$K@d4mLS zbThDwt+4g%@H`<+xDt$1e1I(h-f3n23rzVpmhlf~&qXlfX{Wln%caQZ!=t*mj!IBt z{+fB_5rO`!b*LH#32W8rxp6#3Y?>O5VGOU6RWpxH{O4^JpPM2IPSFyHVBxxw5w?X=SWCJ zpRZ*#HDl`yj%z11{|`*(KWK>m+0Oq5$Pt93p8+;EzPs1lM)7C&6iCAR#|Fz$YZ`1g)HV|VQx^emw#rwEP9qTOVG{CU{ z2rJ+j8Dp#2))%DoQM^c5+69ORQMsr?H338@1d0#>J7M{p%wtJ%Kt8ZjcaO?~7+KLW zgBfPsfxyil>f5q#ae4R+M9uJlS!w}#hRzsnQKOk29ub0hr3-z1mVu?%dkP0buE)$6 z@Gzqu)$R>7%$>N1-HL`xSESg^6}aRPS<5Y0Hk!`HLLg{L7C)?cUfi^`GCFceXKo9a>{tvVRTXq_(dZbT#SRb|S$UDI+W=*R zD;G56CR9ABn`VFq)<7GXpsdZuZ(xuJ9aQq~6)!D}Z*?YGf<&y^iR*2_12>HhPumW+ zV+n84{E02{ONzi5L>Q_qwFge^a7Y#4xg!Qcj7seAT8~6_NJ>tIlAjQvt*3DEZ>riLmeq=c$&g&o+!`ln7Scz(ao?EYuVgUeDJPIz-u67;V)OUw*q zV@wEkxxt_9&j_Q7OG_XHR@h~h#@G-WUNll9;)a+I`MM6kPhk+u(_G<0-d98oetc@F zsgykU0r-)GA%r9>&F@?-F?pHEq7%8}Lhf6uM%nePso>5>Gu=*P#JJ?=5hR=!qZCL2 z7Bs#b?(`eJ?qAd*{gC(tKDz_(>#|h0wc27Og}}|F;Cz?|;V|2PMU4=ApN20ka0y1q zQ#FlUYs*)CC{*IIiin3bZDwn$hEQP}?`OU4$46VPl{w+nMU8zZi1+gJ=6c=HH=Zkt zufD#PloJO`+2nvC#XdhivG(n_Gbi8%Kpam)y*66_wnsl9IU*gIW0}-g7~GJ2PQ?5v zF6N`6$A^dbWcD(^U&o0VDiE##I4Wfb-xW1#&TTc(eM87&qXPhq>cTzhd4ROBZQVk4 zaEQde2PG`LgA#OP5@)m`hS7Q*9~DXTo+ttJXc+ehl>nx@Y@l#t6z;lGS4DkQC^B0j znw)g9JLvAbsx@D>+i~>^fJUX7l1>6FH_r$QI?q_)_n=fH9yf$UpfDbV2Gy1OQmJxl zyBTG_FFsfI3#O2=qAIHH9O@)9sf^`n)Qd* zT z;Z};*{Z?{sV-3JY0C>`(a^BvO*5r5xkI+-;@P0C^6JYN;zbqshrpx&p(r#OX^jFRh zij>#y@nKEAH~zK#`@NE^gCWwjdU$Oa`66b>&3XOVf9X~I0FK4!<*qFFubj(M8fAsgaI3Pp>|{OsK2&S z3^hpGJgk2l^LCC~wt`;D-b~HkcRGs8m!n)zF|4FQdTp1NZah7q6aS&-m;tsZzpMwt z`Q*lM7YW)-T(Q%)QxNne>(5KjED!Y33F0Gb`~*3pgMKO*`USS2f_xD*^t62Hf?d&t z4EFRMxbdY$)qZK-+F&54m%0O314!0LXxCiVR+$Xsr!|#yl#Upsp|&&bD#&z!&DWB@ zw7n6|U+YZ)hJtbvS0236-K&$Xk`BDIzi+Ks?~-(pvU8_=?U&!xy|7LfskNz@o4PT! zPT{OL1wfz`prpt`YgafraFi0@OZe{RuL+`V@52@O`sQ!9m}&3rVX0>cLBXw2zr)r50dwV9QhU+&OTQiDO%_E=3 zehY9*y%o(owSjdl&aR}Qo7u}W^w4xLkS%3@y6gcI+DG$+jRk%NBj8Yo`F}Ek2{3O*)DMqmd4EZb zf0HFyEOGOOLY{ng9Wut)&Z)e1>0IV_8a(?>@e>;N_seqg(?h#!ZM~=+; zL*A_PI%3xm5sL(FU`s=3$$INy5>!+p>v8aS95EI5uJ*L`onxBE<#?SGRM7^?d@{6o=1iYsL07;ZO0^)-IknA;X7Jz8=$X0;vgt1%idl3jsC^Py{>_aSSVB7&5HUj%r15v+Il3v`8Q4%1g zs=wWDAC!9pZ=meiwa$scOB1InrPAX`haFKgm`+!fcuj3qqc9ue?1+g;>XwEuk#V9n z^+xl2@vQ_pPn*<5Gm1c6s2Le6h#n!*Ek(%TVdP-?^9ABzRBR$O-p?q)g@}{ewc=UG zBP#wwk87Vc3Jxd~tTw`y{x`YwB6Ta6d&l~5?^!&z`OLW&`*A0Vy=Mge&;rQ$td1~f zfdx`$oO?y1y+_7lTW=XhXy;5@1_63d*m6CSEYPV?Nl@fWJY#a}dQcB6<~y&eun&|N z?Aft3EatO!bC4eOeK;!8rYbCk;YrdidJ^+3pZ88py-0zA?_OlJa+4l=#_K;S-SvnZ zg@+Xhi^7%Epd?YWwtqjF&=1#ARzS(l7f4AG&kYqXUDUiMNz+hWUJ;PM$|wP;Z6#T&S5Uaar{j^bA~}P-<$2E`Uk#yx|4W`EIB^tPY#^DSVuEi?cfK9IDjua9?3-ZDe5+S0MD+-NL$fEpy(Jj`B zB}NYlSxl#7#^x*qn`lmr2P+D%o)tgoc z%|5}N$+Ou5hSO%UEUDcfg-WF?L@ZtTtBz-zrywXPU8JZK zLa5eQOz7c|c2Jq$^*eH9zcNVQQ&JUR$YD9x&Tb%7~VFfnPE3*s8Tig_O=xL7(Dj8V}| zQg)<-Q!XIj7b}i}zRmt<1ei*amJ#A*GVJP4r$Y&Hwm>L*IhBm3K0+M`PsUdKzeGTT zWd&Yv?BtmrmT|eUgKTl1%)(Zp!-p;nr1OMRknwqx9+#Y{<_hi2W*Kn#aAmeYUGz6n zC??^cnYyG6%b}KzA}Tc?SwM0-u^N>!N~^r8BqqKQLXSac#j5%|^PA|2(RpVohz+y` z%ITJnT>~YDLd@MD*>byUGGtbwMi&sd?(*lH)&oVAZbu#i8&3?m4?ls!N|eKHS*dip ze*bYCC#I#1xcPf{6~{qSKvYQg6dj?r?$wfPI^KboX#0DoXj=B7YM>T_idNun+#U<% zq{Vzk^(x_*wPO0>ktEE>TIYd$?8YS05g#g{yoMtcbv$0({XwQYMCu75`?}pqZ*h*K zWVLNJV-0Q3eXfcC46B`1Mg~N1Q6f9F3aTP_xeq>hF_J#7y2)-zoAgYKa%G${kume@ zIwOX^MUziUKZ-yzk`r}_2KL5Hnfx!y!Ai<*=HY|Nd%f>iz{+U!zQ0m;Yu&D1TH*R6 z$lAos@6;WrsfV@rz)6j~K|TO%!5gLkOp??;bW4*dYX(U>?Vy@SB=(x2~lQ`nl z|I)E}J}m*+wa2#nya7>@f>FpVY1p~Z%O&_#Hj`6<*h)3zdp|(S^Y@RVHF)P-|B_~K zK{d|f%rjjf>%?p{Nz69HEm7#KeryZ6Q1Q>X3uM_<%E-D1#k68XCrU%f7W7~#95RfF z@NVg3#hR!}=qlsFHOre}kI+yQ)u36ccad>@oZq&T3lz{{wy6^8886^X}Nz z*JB%wAEc+yvVKIKRy!aKbL5*IKAo;x5C*vIddy6 z;ra5jP4@zC<0vBIT<*pC>rQQ7bN(nFT(Zye6Stzx|2)UG>oX=l^!H4OZT4S;-^b;S zm^J7QF%A>{CL2+}_&c(QJC9(rYv_59MsIaT8hKI)a7|2{l4v!VRge6e3vkt$9`!fHk7}k%K}c0^0E9$W ze7rjwUSX3QJMwtk_RDyj<^70oT7QQn=i$qHt=FBVOUIM><`b>B{6YI5@IJ=F7i;AQ zOiw&r{G8_jgX$4Efto{AYK)FRJJerab`pD_7C>7UtB)#rjsiym16Ktr4JMTH3QtHt z1PK)MS|wd~H}~_mffCjB+MRmmvzhNWn9tp$9pI|ck9=5vGXx zVtL!*mDlxg*^X}qaItwSk#=kr0{Ql6QhmPYMq{{0@O7bSIKvtYHXGfxe`ecuZ~WM{ z=C1qtQ`zkOQDwyUCz8OMUYaeR^f{J2%JZtuA^FMF6v^q+$lsS`^S^n{KaO!8uz&VG z18pn#gEdz^Em2sm(uTwe$eZpBig3NIu_j&nLMRjtb2MS#$Q>^_N4&guz>q`S0Mh$d z9=77SJj;G#pu$AeY}~%2%leoT1wO|-Wg5PBH1$SIoPMY3J6-DA<<3>x72LNwoxCco zN8B#;j5WIID1`J*mn)Nqh#SiOll;2Pb+`?_$r?kC<3F_0>>Rp%fT4}`c>=2scsAZJ zUfg*(69ZgDn{+xQFWcdb%S>w^<)A?0;75a1sd(ZnH%Cvt*!ukQpAX ze%>QoM0qos;{9@XKa%jZF;&=aGDcEcpgy~2kZke%IZ^>iGX=5LO_Q3(@fD4RgADlg zkDQJE@Tv*?3CgnV9ndBINNNv2j_bQ&Abs!DQcsNEmbmB0msn7X@vtnvi?GJmE|o1D zIvXlM;^I0qY3hC1&0!c8opXDF%Vk`lwr63*!=!IUrKGd%}nd4xf2J=V;M4s>E&JaCeWGOf-gaIAffT#v%6co*MuiU z7oMa-)Oh;^^77c<3wYwqmg=F~^UTZ$C?u5TS>I8;LA;i@*V#Es419Ou?~vP$Wax6} zcLK)S^EG^-(yFE~Aegiov4wNS$15*jC_!Z_bg7Z1iV3izUpkYWFoM#M*G|}Lc<#2j z7AHumlCG=CZs8Xne;F7?<#@314^^G=7VQj;6pWf|w{KZgo-x;1& zmc`y2+h)6Ud5WRotCANaKF6zkQ`eZti4*koOlCh^ojOR3WMqdE-I8{6>Z_N;{@Nid zfkt*r9pr$*Ll3M}|7a2)>>W}m$)~TtoyjQwsqY5aBR}|FztOK(?4^H`doKzhLEu}- z`)U9D=@MjzukPBlT*35sCM`d}P}29oue?g??CL;RE8ri#MFN@Z!5Up|U?5mIsr6Yf zQznL~gD$^!8Xl&9qt~C*iu>y+&k0k`1L{v+uGP*Y$nO4i+4!61;Ulbq5YnhgKKbXD z@896!{nge3vtO0>VJB@)wow&Jf_quR>U%^!BII7U}_+&RDm;`zM>r9w{M%-{8>#fL#tFkl`c!g*=S`RB54 z=T!G;J>!w@t~I96>x>^?$Cv=hO_rw7IXf)= zJrRNbJ0#ob%UIKd|FMnXjvj%-g{fWWwF_PLtmUvxQ68Q_FP;09F>=_ZgsCNwj$h`G zb37~!htTXv`QiFAf$>u~!oGQhW||W&U;pko{!J{w=kSm^E~m)HN#(_gf6>xfLJxHL z3EP{VpLTD#arcZ>gzvp4H|+-%}{=e+0q zI`KtxT-8@ZbyrnpR#s-EFgEs5pvlqs?;JXBuZgdH_hq2znzbD;V`T7ReV%plyzThq zHN;S0F_{u;0_X>Miw(R2xp|u1T=LZ~b@;LBOtMF#$;~|ypD%H6eQcSNr~HvnvWFkB zvoy_QZ(|79S$3yixV(wWjKh(Tq({UIWZ z7e`(3ItITde??(Eb|3K_)2%(FE0QCBeS&eZhw-I!s12nQ9Xg6 zWOF{HbFHF!Pk!&8FJsmDhTmvhxVWF+^j?8X|w{CSZElAHEmsL@LjN5B|3 zd9$s-ue+kM`U^wvqO%KJX?~HNjqj-(F&=u^YB9?LL%i zE=OLl?dJN4eO~Y5Dzsk*TPgVSgdLR-s#o-_UIp2+Ow-a=IKB-ryK^;wFU|=tE&?83 zR+6m3m!>q|pj(h2obfR^-XoD;t6?4nIOIq?L*WJ(2B21QiZ17J0=jJ&+Y%x=3{w<3 zb1lh&=#<-k_7^+IJD0r~xrE3Tyu!eRLJt_;RN8OmON-%?&t4lGb~&tMf%5ug5OlM= zy)B=NsOoXxcTgi~mwqE)@p#RN%d94=X1uqL8B}!~b_0t5{GokW43~(Q)1K1vT9KS1 ze$VS^!{YI_vWQhhsIfsfSBNPOV=`uS#&Kt*rj=Xu%!WTRR0``c(yUe>XQIyKVfn$k z_{h2!g=*y>^%ZYjX_j+)0?&#S>Bg%}WYF*Qfdiro&udV;qEo-(m z(n;Isc;>fV`3L32K4rfp{sj#)zEs-T^8cZk* zcr>~1b8jUroEFqg?xlg)V0ldNFHTdv;_Fb8_v@_h{g85c)MH5u3Hxq>cPSW^-C+ZL zBdkEvZ#(CxUT@P49?Ji=fna81f|sgkYXnlX)ydz}0=iOrGa*%(H@{`AJe)n2S1o}? z2ylcL%PXNU>&%?*vX<9=(rxKxKzVViS~}iM7NplQmH!E;!s-5PvtRR_cIM?`fI~y z_f~)H%AP+_W{?u1UPjP4xS|8YG_wFlxBQXWa`#xI%@=>Cb2*6K%dJk_$Eyh1QKtb{ z|2#vzBBXhy-b79n>8?EYm|+8nl{pwatH+k)6A7YO6z!_vqLkEKim>{W#A!oR{!@VD zjUA4Jgu3Orf^uRNWeK;90%+yTMQOrKdv|md$TsTyKDF!|&e9<$Q#k=TE`Km;z~A>? zjpklAb)pj?;$t0Yjiw0xcj6Xd2l!$kv$9qjA=9L0k03Zb+(GkGL2t?43Wms<8O&kS zmL9d+tQnv19VYlEgtwDL1*da<=HbUwtBYy|MEl(&x-{wx#^ae|H;O zuRMHeHkx|Lid2|g()(BTRJ<$fcuI+d2&XzeRIh3gSCKbS#y!va!q<*};FFp5_7aSE zGV|txSKyll&d@H{Z8gO9L+B>2Jrd=QquD6r{)rA2J#TWix$EELM7M|UDcHhdIDJp8Id8dhFYg|!Vx3lH zUgEZey&B3?OH}Chp}WUz_CH+7zxvkE2WewnRpB(py!hY>)&A{Jn}4E?Lh!~n+WM#0 z$h>~>>-45tq0V739~Pdbpxe|@6Zk{?Yivi`Vi8}lv9GV*=H=GwW*}qn$v}>+qRV%V zC#WRk1+m0q8TNF0RrWbyy}!JC^H=S}7G~~55YCUOt*!b}kg%&gI_&1m3n)BdmD_?Q zspiFT1n@_uz%39Fx5tq(LAo^Q5kBZQa2`#iMoJ=S9r8y|Y^-jSw~$$ama)>pH3iRl z4=<|P6_J!(tLUsH#@dd4TNW1MB3yl}*+FW4P@LzYR39_kbxN54qs3vv1!P7{a(tVvw`HQ{izkSqkwvE*lqJJ`);n< zM@I1A<#{E}Mm}HOoQ^=Q+@AMi?_K>f0uW^=?H&*b+jS02$+N#+P?My zX920H-(P+o3iaST-@~$@|5PeU0f3V-5Ducqa{^+7}aA~E%z?ga6 z8SiYh{rGFSbNX)BHMcJ$P95_^3=mDcY@KZUtQh#MB!A$CBrL+Z zuHs5;uf|!3?f(^q3~DON0^=< z#}?RrZKdDFLl8^7t>#kD{&!jxLi+DuQ#k-Il~0Q!&pS8NO|?4<2t{XG)$Sp}b{h1R zhZkKPRH-Te73{yT|0}H=C0>)rw&FIUQ6+=95E~3_)l=~MqLatKg2eN$C&qPLdyIRw z+Fgw_SymkMXqWy9VP-q?-Jd#{CWG?gMv?yo*Z(0AXvJ!ZvbTKp%7??o-g6uRXm&t- zWfcfmr=>S3^sjS4py8e39&TCtIclb8uktpRG|F1x%$%%N#+AqXay9{5)M*^X(yvIMIlxNw1a3S^o85 z8|Q%3P2s5WLNAOjdK(kU!XV=XXy|W$5b=R9q^Uy+LuS@K32^_N<9_QN#kUgn%%%-J zIRwi&ZG%YvM|jY*D;^6AtHgDkQEXX{KB@=m$fzfMeCr%#B~bf*B&BiD$V~z%>V9!Aa8u_^Y86@RbF3q^B=Iop@B9Z@STDu9Qt;v1%!|wTjYEZ9d?&< z-{0S%R*hBtBHYXriCS^#jJfa)Yq4*~Nu!XOz#O~w8`1PA`4x2rOPV6yU3stcTafZn z5WP_4K2Nkg|7cD7Csp}~K!_e4Y&<)UP3An^@c=%0*sANvhc8{N4n5J`0Bk{v4oI)y$b+lt>~gUZK`Lc@!oq+!+k-%c z!;aKu#Sg>gQxqg{RBmJS zV~`~y0=}=_1cSssBaBl0%U`3?DAg=;EB`Y+CD~Xs0HvPv0gxt+SPHqt9F4K~9~c6& zKcr+}wJeH1WTaXC5~MBk{YlHHp{SB+e<&1TpsupCPC$USfJPUh{*`Rs!QH>7E(EGq zf8f9MC=-J6PDsK*;KCw^OBf0!qzO1g3plnlc;seF*ucf9RhXt z4oQ)s?~zA|r%W8>q(^B@a69Strxf4!FqfRzO<2RW=|)WrQ<>h+bYNnQt6(=s3D)vp z%)@a90R`-w^!F4Oy^ZDwmoD1Z{?ND5PLMim%D%_^rrPWv5K0~wab-$`T1|2U4{?c% ztwy}BC;A%_XoX_=(1(y@2_|c%NiT>j2IAvhwiIzQZ$G;GK7k4#g0}+&6V19(*nhOV z6U=1~50D-{jD!VrF~$)QQYoV2t{Js|yw-*`lE#+Gqu`;$G~D+rYIoSrz3tz;#h(Ik zJ6_Z$Jag$WbapB!{(49+H$KvKppxCn#Ng3!{Fg_m} zJN?1__K+5G$NkD}_Ewn*MU@3)G^a;3gWo18qz{6hPzz;U8m|B|9hpesB|)K&DR12M z6elk$3f)8v8ut@MhvcG1)H~!!72KMJu5tO@5W`HBF#d`LJ^1Z>7TPpMTJ}py5Dh3; zzmy-pxF+2eP_f0@?bsm%bg7K;KqM8dvPX8$5fP{*Ken{OX4DEqCo#4IlC){|k&V<7 z{II|NTa+(eW(6y8o$kQm7ZH$d$!s;mEVIQ{Un z<(PWx^XJC#&>&PSMsw3*%862DW!$=$ z%j|BEFu|QIaBdK-(I_#VXw>iIM)5S}|^8@*j6yhGI6Z%NGcv$;9%v-}q5wsfshDPXDf*S09#JEZ&aw&VL|05IC*dv)L_Ba2Jxcgtp=7&Vknm4bik|te+e2(22 z4ys?{O(PXytRA-ta47#rF8*&vj2YDa0XT5!=IRVxzARmDtM{d)z5|&O&D#Il2YU7g zdRC31iRBKE4uYE4#yRp)xn6s^a*YX<;D1m2|5viH*trVWx?IKGV2lb5#XRl2Zjs4o z=9=b#1QmJN|JShpmCpYEHyR9p>Gd}mKr{C*H3Vj6D82439f^#%}@g?DodBd3QokMMamxQ!oe^vOPNkOgJ^70?41x1+y{yXWBnzd1u~53l2kseva9tZj+7SF8nwwKS?)1cb;^=u7qI zu|f3Ja$NVLMN-pIKRA+J;L9>45EB+DUs(5i^uqYiEO|u2E!I02Lj-@RYHYTUx)9NC zK#>w*y7fYuCV+z>iLz|(H$`&Jo`c>dbocB90*QM-8;+4UL25WsNhSZ&j7Ahg)2>c~ z%lwGEhbiFSYBu?}!#t5-m|SVZ_u-Z%FT0>6wnZ z&^H~-I+5}?Dp0OC2P@TP3)4`~G16&oQvN7S&Dm?(GGJjW=36<{`K^>S@Ipm%(F~)H z_Pa7#01X2}B{r8h0Zlm}$&EM!=_PjL%K5;NlQDZLPDDg4$;W^GRt*xHi;K&%YdhR3VmiHoLww-mv4yF>_kx!n`|mZCD@E};zq?YBzcH%4G+hrpVX7C{zwGm9O` zbR2}80-fo4bWu8=rEZ47w+YF9JiBCyPV2*?nMa1Jiw-BkscT^_&DU`oj&?*N{0q!~ zhWuNmOHWqG8nCsOQ&suP@+jysF%_a74Zu3SDX^DW!k!x^xHF|^qfg{06HT@5J4CpMOD+TN zj~*W!RB)Am{)r&2OVNs*Mj@91rjS7BF}YH|(eIyD0@Tk(w6q8eefFrJ{a^n!;Qair zFF>KMPI{5V+ejmx@7+*ph(!!KkuTSQzBjJ^ROX5Sgc7OwxoSsrxt>v%WW!*C|kz!;a{y%M4K@ zW3d8okh#QC0^ai=%Ax-qcvLQriB>mm;NAd_j&J-2J5=|lU8CmCfD zO31HWS-Fz86$6u>9vn_zW6-Dwcg8;oAV`J!+{0^?@Q-?rmix=!K?N(nxxHCOZh8n5 z?CJgewM*r229~P<~*2KIP-!)L_vCXaHU9ybX8M0=*LmFh zn2e{)S+Xwq0Fm(Xr|D&X_n|Dq;o^AHZ3lzwqth2Z{SmYL^T)A`*Y_tTqLzP60{&#dPvV&`@e7�y^v594-Mz{NjfFfZsc-c6AoVc57Idk3oq- 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 aU!e3^S?L9bIC&tzOHx!;q*6%N|NjA<=l(?i literal 0 HcmV?d00001 diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 3f6477839..8f213243d 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -1242,6 +1242,8 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) var json = JsonConvert.SerializeObject(lev, settings); ; } + + AasxIntegrationBaseWpf.CountryFlagWpf.LoadImage(); } private void ToolFindReplace_ResultSelected(AasxSearchUtil.SearchResultItem resultItem) diff --git a/src/AasxWpfControlLibrary/AnyUiWpf.cs b/src/AasxWpfControlLibrary/AnyUiWpf.cs index 4e0a55eec..c81447bcb 100644 --- a/src/AasxWpfControlLibrary/AnyUiWpf.cs +++ b/src/AasxWpfControlLibrary/AnyUiWpf.cs @@ -456,7 +456,8 @@ private void InitRenderRecs() few.Margin.Bottom + cntl.Padding.Bottom); } } - wpf.Children.Add(chw); + if (chw != null) + wpf.Children.Add(chw); } } } @@ -887,6 +888,21 @@ private void InitRenderRecs() } }), + new RenderRec(typeof(AnyUiCountryFlag), typeof(Image), (a, b, mode, rd) => + { + if (a is AnyUiCountryFlag cntl && b is Image wpf) + { + if (mode == AnyUiRenderMode.All || mode == AnyUiRenderMode.StatusToUi) + { + + wpf.Source = AasxIntegrationBaseWpf.CountryFlagWpf.GetCroppedFlag(cntl.ISO3166Code); + + wpf.Stretch = Stretch.Fill; + wpf.StretchDirection = StretchDirection.Both; + } + } + }), + /*new RenderRec(typeof(AnyUiCountryFlag), typeof(CountryFlag.Wpf.CountryFlag), (a, b, mode, rd) => { if (a is AnyUiCountryFlag cntl && b is CountryFlag.Wpf.CountryFlag wpf From cda3ac67691d69166dd662168c7da80c48b7da51 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Fri, 6 Sep 2024 12:44:06 +0200 Subject: [PATCH 03/99] * added heading4 to AsciiDoc export --- src/AasxPluginExportTable/Smt/ExportSmt.cs | 2 ++ .../Definitions/DefinitionsAsciiDoc.cs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/AasxPluginExportTable/Smt/ExportSmt.cs b/src/AasxPluginExportTable/Smt/ExportSmt.cs index 419e010ec..d24152140 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmt.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmt.cs @@ -406,6 +406,8 @@ public void ExportSmtToFile( ProcessTextBlob("=== ", blob); if (semId.Matches(defs.CD_Heading3.GetCdReference(), mm)) ProcessTextBlob("==== ", blob); + if (semId.Matches(defs.CD_Heading4.GetCdReference(), mm)) + ProcessTextBlob("===== ", blob); } if (sme is Aas.IFile || sme is Aas.IBlob) diff --git a/src/AasxPredefinedConcepts/Definitions/DefinitionsAsciiDoc.cs b/src/AasxPredefinedConcepts/Definitions/DefinitionsAsciiDoc.cs index a49b3b31d..7b7d0f96e 100644 --- a/src/AasxPredefinedConcepts/Definitions/DefinitionsAsciiDoc.cs +++ b/src/AasxPredefinedConcepts/Definitions/DefinitionsAsciiDoc.cs @@ -28,6 +28,7 @@ public Aas.ConceptDescription CD_Heading1, CD_Heading2, CD_Heading3, + CD_Heading4, CD_ImageFile, CD_GenerateUml, CD_GenerateTables; @@ -67,6 +68,11 @@ public AsciiDoc() "http://admin-shell.io/aasx-package-explorer/functions/asciidoc/heading3/1/0", @"Heading with level 3 in AsciiDoc. That is: sub sub section."); + CD_Heading4 = CreateSparseConceptDescription("en", "IRI", + "Heading4", + "http://admin-shell.io/aasx-package-explorer/functions/asciidoc/heading4/1/0", + @"Heading with level 4 in AsciiDoc. That is: sub sub sub section."); + CD_ImageFile = CreateSparseConceptDescription("en", "IRI", "ImageFile", "http://admin-shell.io/aasx-package-explorer/functions/asciidoc/imagefile/1/0", From 7f63aeca0459f6aac0f7d457bd73b935d1cebef5 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Wed, 11 Sep 2024 21:03:51 +0200 Subject: [PATCH 04/99] * post build commands for AsciiDoc * View command for AsciiDoc --- src/AasxPackageExplorer/debug.MIHO.script | 5 +- .../options-debug.MIHO.json | 16 +++++-- .../AasxPluginExportTable.options.json | 9 +++- .../ExportTableOptions.cs | 2 + src/AasxPluginExportTable/Smt/ExportSmt.cs | 47 ++++++++++++++++--- .../Smt/ExportSmtRecord.cs | 10 +++- 6 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 45dfb185e..86108d204 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -4,14 +4,15 @@ // Tool("ExportTable", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.adoc", "Preset", "AsciiDoc"); // Select("Submodel", "First"); // Select("Submodel", "Next"); -// Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "ExportHtml", "true"); // Tool("Exit"); // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\BatteryPass-spiel.ttl"); // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); +// Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); Select("Submodel", "First"); -Select("Plugin", "First"); // Select("Submodel", "Next"); +// Tool("exportsmtasciidoc", "File", "C:\Users\homi0002\Desktop\tmp\new.zip", "ExportHtml", "true", "ExportPdf", "false", "AntoraStyle", "false", "ViewResult", "true"); +// Select("Plugin", "First"); // Select("SME", "First"); // Tool("aas-elem-copy"); // Tool("aas-elem-paste-below"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 5dbf588df..1ed39b9a9 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -39,9 +39,9 @@ // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_TechData_Work\\IDTA 02003-1-2_SubmodelTemplate_TechnicalData_v1.2__with_Draft_1_3_and_AsciiDoc_v01.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Draft_v22.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Examples_v08.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_v20_spiel.aasx", + "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_v20_spiel.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\210_Copy_Paste\\Sample_AAS.aasx", - "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\310_Plugin_DNP\\Sample_AAS.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\310_Plugin_DNP\\Sample_AAS.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\200_Find_Replace\\Sample_SMT.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Examples_v08.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\Syn2tecMachine_P2518_AAS__V3_DL2.aasx", @@ -408,7 +408,7 @@ ] }, { - "Name": "Test 7", + "Name": "AsciiDoc - Just export (Ctrl+Shift+7)", "Lines": [ "Tool(\"LocationPush\");", "Select(\"Submodel\", \"First\");", @@ -416,6 +416,16 @@ "Tool(\"ExportSmtAsciiDoc\", \"File\", \"C:\\Users\\homi0002\\Desktop\\tmp\\new.zip\");", "Tool(\"LocationPop\");" ] + }, + { + "Name": "AsciiDoc - Export and view (Ctrl+Shift+8)", + "Lines": [ + "Tool(\"LocationPush\");", + "Select(\"Submodel\", \"First\");", + "Select(\"Submodel\", \"Next\");", + "Tool(\"ExportSmtAsciiDoc\", \"File\", \"C:\\Users\\homi0002\\Desktop\\tmp\\new.zip\", \"ExportHtml\", \"true\", \"ExportPdf\", \"false\", \"AntoraStyle\", \"false\", \"ViewResult\", \"true\");", + "Tool(\"LocationPop\");" + ] } ], "ScriptLoglevel": 2, diff --git a/src/AasxPluginExportTable/AasxPluginExportTable.options.json b/src/AasxPluginExportTable/AasxPluginExportTable.options.json index 81fe64067..0a131cc96 100644 --- a/src/AasxPluginExportTable/AasxPluginExportTable.options.json +++ b/src/AasxPluginExportTable/AasxPluginExportTable.options.json @@ -3,8 +3,13 @@ "SmtExportHtmlCmd": "docker", "SmtExportHtmlArgs": "run -it -v %WD%:/documents/ asciidoctor/docker-asciidoctor asciidoctor -r asciidoctor-diagram -a stylesheet=asciidoc-style-idta.css %ADOC%", "SmtExportPdfCmd": "docker", - "SmtExportPdfArgs": "run -it -v %WD%:/documents/ asciidoctor/docker-asciidoctor asciidoctor-pdf -r asciidoctor-diagram -a pdf-theme=my-theme.yml %ADOC%", -/* + // without line numbers: + // "SmtExportPdfArgs": "run -it -v %WD%:/documents/ asciidoctor/docker-asciidoctor asciidoctor-pdf -r asciidoctor-diagram -a pdf-theme=my-theme.yml %ADOC%", + // with line numbers: + "SmtExportPdfArgs": "run -it -v %WD%:/documents/ asciidoctor/docker-asciidoctor asciidoctor-pdf -r asciidoctor-diagram -r ./extended.rb -a toc=macro -a pdf-theme=my-theme.yml -a env-pdf %ADOC%", + "SmtExportViewCmd": "cmd.exe", + "SmtExportViewArgs": "/c start %WD%\\%HTML% && ping 127.0.0.1 -n 5 >nul", + /* "SmtExportHtmlCmd": "ping", "SmtExportHtmlArgs": "127.0.0.1 -n 6", "SmtExportPdfCmd": "ping", diff --git a/src/AasxPluginExportTable/ExportTableOptions.cs b/src/AasxPluginExportTable/ExportTableOptions.cs index 7dfaae27a..14b450f94 100644 --- a/src/AasxPluginExportTable/ExportTableOptions.cs +++ b/src/AasxPluginExportTable/ExportTableOptions.cs @@ -28,6 +28,8 @@ public class ExportTableOptions : AasxPluginOptionsBase public string SmtExportHtmlArgs = ""; public string SmtExportPdfCmd = ""; public string SmtExportPdfArgs = ""; + public string SmtExportViewCmd = ""; + public string SmtExportViewArgs = ""; public ExportUmlRecord UmlExport = null; diff --git a/src/AasxPluginExportTable/Smt/ExportSmt.cs b/src/AasxPluginExportTable/Smt/ExportSmt.cs index d24152140..b17619cb1 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmt.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmt.cs @@ -219,6 +219,7 @@ protected void ProcessUml(Aas.IReferenceElement refel) pumlName = AdminShellUtil.FilterFriendlyName(refel.IdShort); var pumlFn = pumlName + ".puml"; var absPumlFn = Path.Combine(_locationDiagrams, pumlFn); + var extraAntoraPath = _optionsSmt.AntoraStyle ? "partial$diagrams/" : ""; // make options var umlOptions = new ExportUmlRecord(); @@ -240,7 +241,7 @@ protected void ProcessUml(Aas.IReferenceElement refel) _adoc.AppendLine(""); _adoc.AppendLine($"[plantuml, {pumlName}, svg, id=\"{pumlName}\", {astr}]"); _adoc.AppendLine("----"); - _adoc.AppendLine("include::" + pumlFn + "[]"); + _adoc.AppendLine("include::" + extraAntoraPath + pumlFn + "[]"); _adoc.AppendLine("----"); _adoc.AppendLine(""); } @@ -363,17 +364,37 @@ public void ExportSmtToFile( { try { - _locationPages = Path.Combine(_tempDir, "pages"); - _locationImages = Path.Combine(_tempDir, "images"); - _locationDiagrams = Path.Combine(Path.Combine(_tempDir, "partials"), "diagrams"); + // create a lot of directories + var docRoot = Path.Combine(_tempDir, "documentation"); + Directory.CreateDirectory(docRoot); + var modules = Path.Combine(docRoot, "modules"); + Directory.CreateDirectory(modules); + + var root = Path.Combine(modules, "root"); + Directory.CreateDirectory(root); + + _locationPages = Path.Combine(root, "pages"); Directory.CreateDirectory(_locationPages); + + _locationImages = Path.Combine(root, "images"); Directory.CreateDirectory(_locationImages); + + _locationDiagrams = Path.Combine(Path.Combine(root, "partials"), "diagrams"); Directory.CreateDirectory(Path.Combine(_tempDir, "partials")); Directory.CreateDirectory(_locationDiagrams); _log?.Info(StoredPrint.Color.Black, - "Created dedicated sub-folders for pages, images, partials/diagrams."); + "Created dedicated sub-folders for documentation, modules, root, pages, images, partials/diagrams."); + + // create some boiler plate + var antoraYamlTxt = AdminShellUtil.CleanHereStringWithNewlines( + @"name: IDTA-00000 + title: 'TODO' + version: 'v1.0' + start_page: ROOT:index.adoc"); + + System.IO.File.WriteAllText(Path.Combine(docRoot, "antora.yml"), antoraYamlTxt); } catch (Exception ex) { @@ -432,9 +453,9 @@ public void ExportSmtToFile( var adocText = _adoc.ToString(); // build adoc file - var title = (_srcSm.IdShort?.HasContent() == true) + var title = (!optionsSmt.AntoraStyle && (_srcSm.IdShort?.HasContent() == true)) ? AdminShellUtil.FilterFriendlyName(_srcSm.IdShort) - : "output"; + : "index"; var adocFn = title + ".adoc"; var absAdocFn = Path.Combine(_locationPages, adocFn); @@ -463,6 +484,18 @@ public void ExportSmtToFile( displayContext?.MenuExecuteSystemCommand("Exporting PDF", _tempDir, cmd, args); } + if (_optionsSmt.ViewResult) + { + var cmd = _optionsAll.SmtExportViewCmd; + var args = _optionsAll.SmtExportViewArgs + .Replace("%WD%", "" + _tempDir) + .Replace("%ADOC%", "" + adocFn) + .Replace("%HTML%", "" + adocFn.Replace(".adoc", ".html")) + .Replace("%PDF%", "" + adocFn.Replace(".adoc", ".pdf")); + + displayContext?.MenuExecuteSystemCommand("Viewing results", _tempDir, cmd, args); + } + // now, how to handle files? if (_singleFile) { diff --git a/src/AasxPluginExportTable/Smt/ExportSmtRecord.cs b/src/AasxPluginExportTable/Smt/ExportSmtRecord.cs index 0d96a5f4d..13a8c1bac 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmtRecord.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmtRecord.cs @@ -37,10 +37,16 @@ public class ExportSmtRecord [AasxMenuArgument(help: "If true, will do dedicated sub-folders for images and diagrams.")] public bool AntoraStyle = false; - [AasxMenuArgument(help: "If true, will execute external program to produce HTML from AsciiDic.")] + [AasxMenuArgument(help: "If true, will execute external program to produce HTML from AsciiDoc " + + "using options 'SmtExportHtmlCmd' and 'SmtExportHtmlArgs' with placeholders %WD%, %ADOC%.")] public bool ExportHtml = false; - [AasxMenuArgument(help: "If true, will execute external program to produce PDF from AsciiDic.")] + [AasxMenuArgument(help: "If true, will execute external program to produce PDF from AsciiDoc " + + "using options 'SmtExportPdfCmd' and 'SmtExportPdfArgs' with placeholders %WD%, %ADOC%.")] public bool ExportPdf = false; + + [AasxMenuArgument(help: "If true, will execute external program while in temporary directory " + + "using options 'SmtExportViewCmd' and 'SmtExportViewArgs' with placeholders %WD%, %ADOC%, %HTML%, %PDF%.")] + public bool ViewResult = false; } } From f740514d6713ef23e216e843adc0b7a6e9c26e82 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Wed, 11 Sep 2024 21:05:36 +0200 Subject: [PATCH 05/99] * ROOT for AsciiDoc --- src/AasxPluginExportTable/Smt/ExportSmt.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AasxPluginExportTable/Smt/ExportSmt.cs b/src/AasxPluginExportTable/Smt/ExportSmt.cs index b17619cb1..442962dc2 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmt.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmt.cs @@ -371,7 +371,7 @@ public void ExportSmtToFile( var modules = Path.Combine(docRoot, "modules"); Directory.CreateDirectory(modules); - var root = Path.Combine(modules, "root"); + var root = Path.Combine(modules, "ROOT"); Directory.CreateDirectory(root); _locationPages = Path.Combine(root, "pages"); From 94d5bff4bc7f1545ad41b753293d979c2dc18022 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Fri, 13 Sep 2024 16:11:49 +0200 Subject: [PATCH 06/99] * investigate --- src/AasxAmlImExport/AmlExport.cs | 2 +- src/AasxAmlImExport/AmlImport.cs | 6 +- src/AasxBammRdfImExport/RDFimport.cs | 4 +- ...nv.cs => AdminShellPackageFileBasedEnv.cs} | 253 +++++++++---- .../Extensions/ExtendEnvironment.cs | 18 + .../Extensions/LocatedReference.cs | 14 + src/AasxFormatCst/AasxToCst.cs | 4 +- .../AasForms/AasFormUtils.cs | 2 +- .../AasForms/FormInstance.cs | 26 +- src/AasxIntegrationBase/AasxMenu.cs | 2 +- .../AnyUI/AnyUiMagickHelper.cs | 4 +- .../AasxWpfBaseUtils.cs | 2 +- src/AasxMqttClient/MqttClient.cs | 2 +- .../MainWindow.CommandBindings.cs | 6 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 10 +- .../options-debug.MIHO.json | 15 +- src/AasxPackageLogic/AasxPackageLogic.csproj | 2 + src/AasxPackageLogic/DispEditHelperModules.cs | 4 +- src/AasxPackageLogic/IMainWindow.cs | 2 +- .../MainWindowAnyUiDialogs.cs | 8 +- .../MenuFunction/MenuFuncValidateSmt.cs | 2 +- .../AasxFilePackageContainerBase.cs | 2 +- .../PackageContainerAasxFileRepository.cs | 4 +- .../PackageCentral/PackageCentral.cs | 12 +- .../PackageCentral/PackageConnectorBase.cs | 2 +- .../PackageCentral/PackageContainerBase.cs | 10 +- .../PackageCentral/PackageContainerFactory.cs | 19 + .../PackageContainerHttpRepoSubset.cs | 283 +++++++++++++++ .../PackageContainerListBase.cs | 6 +- .../PackageContainerLocalFile.cs | 4 +- .../PackageContainerNetworkHttpFile.cs | 4 +- .../PackageContainerUserFile.cs | 4 +- .../PackageCentral/PackageHttpDownloadUtil.cs | 343 ++++++++++++++++++ .../PackageContainerFakeAnswers.json | 19 + src/AasxPackageLogic/VisualAasxElements.cs | 30 +- .../AssetInterfaceAnyUiControl.cs | 12 +- .../GenericBomControl.cs | 8 +- .../ContactEntity.cs | 2 +- .../ContactListAnyUiControl.cs | 6 +- .../NameplateAnyUiControl.cs | 6 +- .../NameplateData.cs | 6 +- src/AasxPluginDocumentShelf/DocumentEntity.cs | 6 +- .../ShelfAnyUiControl.cs | 8 +- .../ShelfPreviewService.cs | 4 +- src/AasxPluginExportTable/Smt/ExportSmt.cs | 4 +- .../GenericFormsAnyUiControl.cs | 6 +- .../ImageMapAnyUiControl.cs | 10 +- .../KnownSubmodelAnyUiControl.cs | 12 +- .../WpfMtpControlWrapper.xaml.cs | 6 +- src/AasxPluginPlotting/PlotItem.cs | 2 +- .../PlottingViewControl.xaml.cs | 4 +- src/AasxPluginPlotting/Plugin.cs | 2 +- .../PcnAnyUiControl.cs | 16 +- .../TechnicalDataAnyUiControl.cs | 18 +- .../Convert/ConvertDocumentationHsuToSg2.cs | 2 +- .../Convert/ConvertDocumentationSg2ToHsu.cs | 2 +- .../Convert/ConvertDocumentationSg2ToV11.cs | 2 +- .../Convert/ConvertNameplateHsuToZveiV10.cs | 2 +- .../Convert/ConvertPredefinedConcepts.cs | 2 +- .../Convert/ConvertTechnicalDataToFlat.cs | 2 +- .../Convert/ConvertTechnicalDataV10ToV11.cs | 2 +- src/AasxToolkit/Execution.cs | 8 +- src/AasxToolkit/Extract.cs | 2 +- src/AasxToolkit/Generate.cs | 12 +- .../DiplayVisualAasxElements.xaml.cs | 2 +- src/BlazorExplorer/BlazorVisualElements.cs | 2 +- src/BlazorExplorer/Data/AASService.cs | 6 +- src/BlazorExplorer/Data/AasxInfoBox.cs | 2 +- .../Data/BlazorSession.MainWindow.cs | 2 +- src/BlazorExplorer/Data/BlazorSession.cs | 2 +- 70 files changed, 1068 insertions(+), 252 deletions(-) rename src/AasxCsharpLibrary/{AdminShellPackageEnv.cs => AdminShellPackageFileBasedEnv.cs} (93%) create mode 100644 src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs create mode 100644 src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs create mode 100644 src/AasxPackageLogic/Resources/PackageContainerFakeAnswers.json diff --git a/src/AasxAmlImExport/AmlExport.cs b/src/AasxAmlImExport/AmlExport.cs index cad046b50..d80a7b4f8 100644 --- a/src/AasxAmlImExport/AmlExport.cs +++ b/src/AasxAmlImExport/AmlExport.cs @@ -1129,7 +1129,7 @@ private static void SetInternalLinks( } } - public static bool ExportTo(AdminShellPackageEnv package, string amlfn, bool tryUseCompactProperties = false) + public static bool ExportTo(AdminShellPackageEnvBase package, string amlfn, bool tryUseCompactProperties = false) { // start if (package == null || amlfn == null || package.AasEnv == null) diff --git a/src/AasxAmlImExport/AmlImport.cs b/src/AasxAmlImExport/AmlImport.cs index 75295e3ae..2514d3d08 100644 --- a/src/AasxAmlImExport/AmlImport.cs +++ b/src/AasxAmlImExport/AmlImport.cs @@ -38,7 +38,7 @@ public static string PrintSemantic(CAEXSequence sem) public class AmlParser { - public AdminShellPackageEnv package = null; + public AdminShellPackageEnvBase package = null; private class TargetIdAction { @@ -87,7 +87,7 @@ public IeViewAmlTarget(InternalElementType ie, CAEXObject amlTarget) public AmlParser() { } - public AmlParser(AdminShellPackageEnv package) + public AmlParser(AdminShellPackageEnvBase package) { this.package = package; } @@ -1634,7 +1634,7 @@ public void Start(CAEXFileType caex) } } - public static void ImportInto(AdminShellPackageEnv package, string amlfn) + public static void ImportInto(AdminShellPackageEnvBase package, string amlfn) { // try open file var doc = CAEXDocument.LoadFromFile(amlfn); diff --git a/src/AasxBammRdfImExport/RDFimport.cs b/src/AasxBammRdfImExport/RDFimport.cs index d91c09888..40b5cb69a 100644 --- a/src/AasxBammRdfImExport/RDFimport.cs +++ b/src/AasxBammRdfImExport/RDFimport.cs @@ -24,13 +24,13 @@ namespace AasxBammRdfImExport public static class BAMMRDFimport { - public static AdminShellNS.AdminShellPackageEnv thePackageEnv; + public static AdminShellNS.AdminShellPackageFileBasedEnv thePackageEnv; public static void ImportInto( string rdffn, Aas.IEnvironment env, Aas.ISubmodel sm, Aas.Reference smref) { - thePackageEnv = new AdminShellNS.AdminShellPackageEnv(); + thePackageEnv = new AdminShellNS.AdminShellPackageFileBasedEnv(); List entity_subject = new List(); List autos_list = new List(); List set_list = new List(); diff --git a/src/AasxCsharpLibrary/AdminShellPackageEnv.cs b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs similarity index 93% rename from src/AasxCsharpLibrary/AdminShellPackageEnv.cs rename to src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs index 3dcf74dc3..3c8fce318 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageEnv.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs @@ -246,11 +246,175 @@ public static T DeserializeFromJSON(string data) where T : IReferable // dead-csharp on } + public abstract class AdminShellPackageEnvBase : IDisposable + { + public enum SerializationFormat { None, Xml, Json }; + + protected IEnvironment _aasEnv = new AasCore.Aas3_0.Environment(); + + public AdminShellPackageEnvBase() { } + + public AdminShellPackageEnvBase(AasCore.Aas3_0.Environment env) + { + if (env != null) + _aasEnv = env; + } + + public IEnvironment AasEnv + { + get + { + return _aasEnv; + } + } + + public void SetEnvironment(IEnvironment environment) + { + _aasEnv = environment; + } + + // TODO: remove, is not for base class!! + public virtual void SetFilename(string fileName) + { + } + + // TODO: remove, is not for base class!! + public virtual string Filename + { + get + { + return ""; + } + } + + public virtual Stream GetLocalStreamFromPackage( + string uriString, + FileMode mode = FileMode.Open, + FileAccess access = FileAccess.Read) + { + return null; + } + + public virtual void PrepareSupplementaryFileParameters(ref string targetDir, ref string targetFn) + { + } + + public virtual string AddSupplementaryFileToStore( + string sourcePath, string targetDir, string targetFn, bool embedAsThumb, + AdminShellPackageSupplementaryFile.SourceGetByteChunk sourceGetBytesDel = null, string useMimeType = null) + { + return null; + } + + public virtual byte[] GetByteArrayFromUriOrLocalPackage(string uriString) + { + return null; + } + + /// + /// Ensures: + ///
  • result == null || result.CanRead
+ ///
+ public Stream GetLocalThumbnailStream() + { + Uri dummy = null; + var result = GetLocalThumbnailStream(ref dummy); + + // Post-condition + if (!(result == null || result.CanRead)) + { + throw new InvalidOperationException("Unexpected unreadable result stream"); + } + + return result; + } + + /// + /// Ensures: + ///
  • result == null || result.CanRead
+ ///
+ public virtual Stream GetLocalThumbnailStream(ref Uri thumbUri) + { + return null; + } + + public virtual Stream GetStreamFromUriOrLocalPackage(string uriString, + FileMode mode = FileMode.Open, + FileAccess access = FileAccess.Read) + { + return null; + } + + public virtual ListOfAasSupplementaryFile GetListOfSupplementaryFiles() + { + return null; + } + + public virtual void DeleteSupplementaryFile(AdminShellPackageSupplementaryFile psf) + { + } + + /// + /// Copies/ download contents and will return filenam of temp file. + /// + /// + public virtual string MakePackageFileAvailableAsTempFile(string packageUri, bool keepFilename = false) + { + return null; + } + + public virtual bool SaveAs( + string fn, bool writeFreshly = false, + SerializationFormat prefFmt = SerializationFormat.None, + MemoryStream useMemoryStream = null, bool saveOnlyCopy = false) + { + return false; + } + + /// + /// Temporariyl saves & closes package and executes lambda. Afterwards, the package is re-opened + /// under the same file name + /// + /// Action which is to be executed while the file is CLOSED + /// Format for the saved file + public virtual void TemporarilySaveCloseAndReOpenPackage( + Action lambda, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None) + { + } + + public virtual bool IsOpen + { + get + { + // negative default behaviour + return false; + } + } + + public virtual bool IsLocalFile(string uriString) + { + return false; + } + + public virtual void Close() + { + } + + public virtual void Flush() + { + } + + public virtual void Dispose() + { + } + } + /// /// This class encapsulates an AdminShellEnvironment and supplementary files into an AASX Package. /// Specifically has the capability to load, update and store .XML, .JSON and .AASX packages. /// - public class AdminShellPackageEnv : IDisposable + public class AdminShellPackageFileBasedEnv : AdminShellPackageEnvBase { // Note: the first array element [0] should be conforming the actual spec (for saving) protected static string[] relTypesOrigin = new[] { @@ -276,25 +440,20 @@ public class AdminShellPackageEnv : IDisposable private string _tempFn = null; - private IEnvironment _aasEnv = new AasCore.Aas3_0.Environment(); private Package _openPackage = null; private readonly ListOfAasSupplementaryFile _pendingFilesToAdd = new ListOfAasSupplementaryFile(); private readonly ListOfAasSupplementaryFile _pendingFilesToDelete = new ListOfAasSupplementaryFile(); - public AdminShellPackageEnv() { } + public AdminShellPackageFileBasedEnv() : base() { } - public AdminShellPackageEnv(AasCore.Aas3_0.Environment env) - { - if (env != null) - _aasEnv = env; - } + public AdminShellPackageFileBasedEnv(AasCore.Aas3_0.Environment env) : base(env) { } - public AdminShellPackageEnv(string fn, bool indirectLoadSave = false) + public AdminShellPackageFileBasedEnv(string fn, bool indirectLoadSave = false) : base() { Load(fn, indirectLoadSave); } - public bool IsOpen + public override bool IsOpen { get { @@ -302,12 +461,12 @@ public bool IsOpen } } - public void SetFilename(string fileName) + public override void SetFilename(string fileName) { _fn = fileName; } - public string Filename + public override string Filename { get { @@ -315,19 +474,6 @@ public string Filename } } - public IEnvironment AasEnv - { - get - { - return _aasEnv; - } - } - - public void SetEnvironment(IEnvironment environment) - { - _aasEnv = environment; - } - private class FindRelTuple { public bool Deprecated { get; set; } @@ -631,7 +777,7 @@ public void LoadFromAasEnvString(string content) } } - public enum SerializationFormat { None, Xml, Json }; + // dead-csharp off //public static XmlSerializerNamespaces GetXmlDefaultNamespaces() //{ @@ -643,7 +789,8 @@ public enum SerializationFormat { None, Xml, Json }; // return nss; //} // dead-csharp on - public bool SaveAs(string fn, bool writeFreshly = false, SerializationFormat prefFmt = SerializationFormat.None, + + public override bool SaveAs(string fn, bool writeFreshly = false, SerializationFormat prefFmt = SerializationFormat.None, MemoryStream useMemoryStream = null, bool saveOnlyCopy = false) { // silently fix flaws @@ -1091,7 +1238,7 @@ public bool SaveAs(string fn, bool writeFreshly = false, SerializationFormat pre var mimeType = psfAdd.UseMimeType; // reconcile mime if (mimeType == null && psfAdd.SourceLocalPath != null) - mimeType = AdminShellPackageEnv.GuessMimeType(psfAdd.SourceLocalPath); + mimeType = AdminShellPackageFileBasedEnv.GuessMimeType(psfAdd.SourceLocalPath); // still null? if (mimeType == null) // see: https://stackoverflow.com/questions/6783921/ @@ -1172,9 +1319,9 @@ public bool SaveAs(string fn, bool writeFreshly = false, SerializationFormat pre /// /// Action which is to be executed while the file is CLOSED /// Format for the saved file - public void TemporarilySaveCloseAndReOpenPackage( + public override void TemporarilySaveCloseAndReOpenPackage( Action lambda, - AdminShellPackageEnv.SerializationFormat prefFmt = AdminShellPackageEnv.SerializationFormat.None) + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None) { // access if (!this.IsOpen) @@ -1274,7 +1421,7 @@ public void BackupInDir(string backupDir, int maxFiles) } } - public Stream GetStreamFromUriOrLocalPackage(string uriString, + public override Stream GetStreamFromUriOrLocalPackage(string uriString, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read) { @@ -1286,7 +1433,7 @@ public Stream GetStreamFromUriOrLocalPackage(string uriString, return System.IO.File.Open(uriString, mode, access); } - public byte[] GetByteArrayFromUriOrLocalPackage(string uriString) + public override byte[] GetByteArrayFromUriOrLocalPackage(string uriString) { try { @@ -1306,7 +1453,7 @@ public byte[] GetByteArrayFromUriOrLocalPackage(string uriString) } } - public bool IsLocalFile(string uriString) + public override bool IsLocalFile(string uriString) { // access if (_openPackage == null) @@ -1321,7 +1468,7 @@ public bool IsLocalFile(string uriString) private static WebProxy proxy = null; - public Stream GetLocalStreamFromPackage(string uriString, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read) + public override Stream GetLocalStreamFromPackage(string uriString, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read) { // Check, if remote if (uriString.ToLower().Substring(0, 4) == "http") @@ -1486,7 +1633,7 @@ public long GetStreamSizeFromPackage(string uriString) /// Ensures: ///
  • result == null || result.CanRead
/// - public Stream GetLocalThumbnailStream(ref Uri thumbUri) + public virtual Stream GetLocalThumbnailStream(ref Uri thumbUri) { // access if (_openPackage == null) @@ -1517,27 +1664,9 @@ public Stream GetLocalThumbnailStream(ref Uri thumbUri) } return result; - } - - /// - /// Ensures: - ///
  • result == null || result.CanRead
- ///
- public Stream GetLocalThumbnailStream() - { - Uri dummy = null; - var result = GetLocalThumbnailStream(ref dummy); - - // Post-condition - if (!(result == null || result.CanRead)) - { - throw new InvalidOperationException("Unexpected unreadable result stream"); - } - - return result; - } + } - public ListOfAasSupplementaryFile GetListOfSupplementaryFiles() + public override ListOfAasSupplementaryFile GetListOfSupplementaryFiles() { // new result var result = new ListOfAasSupplementaryFile(); @@ -1648,7 +1777,7 @@ public static string GuessMimeType(string fn) return content_type; } - public void PrepareSupplementaryFileParameters(ref string targetDir, ref string targetFn) + public override void PrepareSupplementaryFileParameters(ref string targetDir, ref string targetFn) { // re-work target dir if (targetDir != null) @@ -1664,7 +1793,7 @@ public void PrepareSupplementaryFileParameters(ref string targetDir, ref string /// materialize embedding. /// /// Target path of file in package - public string AddSupplementaryFileToStore( + public override string AddSupplementaryFileToStore( string sourcePath, string targetDir, string targetFn, bool embedAsThumb, AdminShellPackageSupplementaryFile.SourceGetByteChunk sourceGetBytesDel = null, string useMimeType = null) { @@ -1714,7 +1843,7 @@ public void AddSupplementaryFileToStore(string sourcePath, string targetPath, bo } - public void DeleteSupplementaryFile(AdminShellPackageSupplementaryFile psf) + public override void DeleteSupplementaryFile(AdminShellPackageSupplementaryFile psf) { if (psf == null) throw (new Exception("No supplementary file given!")); @@ -1732,7 +1861,7 @@ public void DeleteSupplementaryFile(AdminShellPackageSupplementaryFile psf) } } - public void Close() + public override void Close() { _openPackage?.Close(); _openPackage = null; @@ -1740,18 +1869,18 @@ public void Close() _aasEnv = null; } - public void Flush() + public override void Flush() { if (_openPackage != null) _openPackage.Flush(); } - public void Dispose() + public override void Dispose() { Close(); } - public string MakePackageFileAvailableAsTempFile(string packageUri, bool keepFilename = false) + public override string MakePackageFileAvailableAsTempFile(string packageUri, bool keepFilename = false) { // access if (packageUri == null) diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index 3d07975a9..8701094c0 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -964,6 +964,24 @@ public static IEnumerable FindAllReferences(this AasCore.Aas3_ yield return new LocatedReference(cd, r); } + // TODO: Integrate into above function + public static IEnumerable FindAllSubmodelReferences( + this AasCore.Aas3_0.IEnvironment environment, + bool onlyNotExisting = false) + { + // unique set of references + var refs = new List(); + foreach (var aas in environment.AllAssetAdministrationShells()) + foreach (var smr in aas?.Submodels) + refs.AddIfNew(new LocatedReference() { Identifiable = aas, Reference = smr}); + + // only existing + foreach (var lr in refs) + if (!onlyNotExisting + || null == environment.FindSubmodel(lr?.Reference)) + yield return lr; + } + /// /// Tries renaming an Identifiable, specifically: the identification of an Identifiable and /// all references to it. diff --git a/src/AasxCsharpLibrary/Extensions/LocatedReference.cs b/src/AasxCsharpLibrary/Extensions/LocatedReference.cs index 26916c5b7..4fcc6cc97 100644 --- a/src/AasxCsharpLibrary/Extensions/LocatedReference.cs +++ b/src/AasxCsharpLibrary/Extensions/LocatedReference.cs @@ -6,6 +6,10 @@ This source code is licensed under the Apache License 2.0 (see LICENSE.txt). This source code may use other Open Source software components (see LICENSE.txt). */ +using System.Linq; +using System.Collections; +using System.Collections.Generic; + namespace Extensions { public class LocatedReference @@ -20,4 +24,14 @@ public LocatedReference(IIdentifiable identifiable, IReference reference) Reference = reference; } } + + public static class LocatedReferenceExtensions + { + public static void AddIfNew(this IList refs, LocatedReference newLR) + { + var same = refs?.FirstOrDefault((lr) => lr?.Reference?.Matches(newLR?.Reference, MatchMode.Relaxed) == true); + if (same == null) + refs.Add(newLR); + } + } } diff --git a/src/AasxFormatCst/AasxToCst.cs b/src/AasxFormatCst/AasxToCst.cs index 7aec6edf9..c1eeea564 100644 --- a/src/AasxFormatCst/AasxToCst.cs +++ b/src/AasxFormatCst/AasxToCst.cs @@ -21,7 +21,7 @@ namespace AasxFormatCst { public class AasxToCst { - protected AdminShellPackageEnv _env; + protected AdminShellPackageFileBasedEnv _env; protected int _customIndex = 1; public string CustomNS = "UNSPEC"; @@ -267,7 +267,7 @@ private void RecurseOnSme( } public void ExportSingleSubmodel( - AdminShellPackageEnv env, string path, + AdminShellPackageFileBasedEnv env, string path, Aas.Key smId, IEnumerable cdReferables, CstIdObjectBase firstNodeId, diff --git a/src/AasxIntegrationBase/AasForms/AasFormUtils.cs b/src/AasxIntegrationBase/AasForms/AasFormUtils.cs index 75e26c01f..1346019e3 100644 --- a/src/AasxIntegrationBase/AasForms/AasFormUtils.cs +++ b/src/AasxIntegrationBase/AasForms/AasFormUtils.cs @@ -213,7 +213,7 @@ private static void RecurseExportAsTemplate( } } - public static void ExportAsTemplate(AdminShellPackageEnv package, string fn) + public static void ExportAsTemplate(AdminShellPackageFileBasedEnv package, string fn) { // access if (fn == null || package == null || package.AasEnv == null) diff --git a/src/AasxIntegrationBase/AasForms/FormInstance.cs b/src/AasxIntegrationBase/AasForms/FormInstance.cs index e4f1ffb8a..b3520a200 100644 --- a/src/AasxIntegrationBase/AasForms/FormInstance.cs +++ b/src/AasxIntegrationBase/AasForms/FormInstance.cs @@ -320,7 +320,7 @@ public FormDescInstancesPair FindInstance(FormInstanceListOfSame searchInst) /// public List AddOrUpdateDifferentElementsToCollection( List elements, - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false) { // will be a list of newly added elements (for tracing) var res = new List(); @@ -480,7 +480,7 @@ public void PresetInstancesBasedOnSource(List sourceElemen /// Render the form description and adds or updates its instances into a list of SubmodelElements. /// public List AddOrUpdateSameElementsToCollection( - List elements, AdminShellPackageEnv packageEnv = null, + List elements, AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false) { // access @@ -1057,7 +1057,7 @@ public void PresetInstancesBasedOnSource(List sourceElemen /// public List AddOrUpdateDifferentElementsToCollection( List elements, - AdminShellPackageEnv packageEnv = null, + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { @@ -1165,7 +1165,7 @@ protected void InitReferable(FormDescSubmodelElement desc, Aas.ISubmodelElement /// /// True, if a new element shall be rendered from the instance sme. public virtual bool ProcessSmeForRender( - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { if (this.sme != null && Touched && this.sourceSme != null && editSource) { @@ -1183,7 +1183,7 @@ public virtual bool ProcessSmeForRender( /// public virtual List AddOrUpdateSmeToCollection( List collectionNewElements, - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false) { // typically, there will be only one SME var res = new List(); @@ -1280,7 +1280,7 @@ public FormInstanceListOfDifferent GetListOfDifferent() /// public override List AddOrUpdateSmeToCollection( - List elements, AdminShellPackageEnv packageEnv = null, + List elements, AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false) { // SMEC as Refrable @@ -1312,7 +1312,7 @@ public void PresetInstancesBasedOnSource(List sourceElemen /// Render the list of form elements into a list of SubmodelElements. /// public List AddOrUpdateDifferentElementsToCollection( - List elements, AdminShellPackageEnv packageEnv = null, + List elements, AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false) { if (this.PairInstances != null) @@ -1422,7 +1422,7 @@ public FormInstanceProperty( /// producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -1616,7 +1616,7 @@ public FormInstanceMultiLangProp( /// the new values instead of producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -1784,7 +1784,7 @@ public FormInstanceFile( /// producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -1969,7 +1969,7 @@ public FormInstanceReferenceElement( /// the new values instead of producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -2108,7 +2108,7 @@ public FormInstanceRelationshipElement( /// the new values instead of producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -2270,7 +2270,7 @@ public FormInstanceCapability( /// the new values instead of producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); diff --git a/src/AasxIntegrationBase/AasxMenu.cs b/src/AasxIntegrationBase/AasxMenu.cs index a0f2c0ee5..f06c14b68 100644 --- a/src/AasxIntegrationBase/AasxMenu.cs +++ b/src/AasxIntegrationBase/AasxMenu.cs @@ -844,7 +844,7 @@ public class AasxMenuActionTicket /// /// Filled by the currently selected element. /// - public AdminShellPackageEnv Package; + public AdminShellPackageEnvBase Package; /// /// Filled by the currently selected element. diff --git a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs index f140be9c0..ab6963e93 100644 --- a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs +++ b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs @@ -83,7 +83,7 @@ public static AnyUiBitmapInfo CreateAnyUiBitmapFromResource(string path, return null; } - public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageEnv package, string path) + public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageFileBasedEnv package, string path) { if (package == null || path == null) return null; @@ -113,7 +113,7 @@ public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageEnv pac // TODO (MIHO, 2023-02-23): make the whole thing async!! public static AnyUiBitmapInfo MakePreviewFromPackageOrUrl( - AdminShellPackageEnv package, string path, + AdminShellPackageFileBasedEnv package, string path, double dpi = 75) { if (path == null) diff --git a/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs b/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs index 5eaae3b2b..cb8e0b520 100644 --- a/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs +++ b/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs @@ -137,7 +137,7 @@ public static IEnumerable LogicalTreeFindAllChildsWithRegexTag } } - public static BitmapImage LoadBitmapImageFromPackage(AdminShellPackageEnv package, string path) + public static BitmapImage LoadBitmapImageFromPackage(AdminShellPackageFileBasedEnv package, string path) { if (package == null || path == null) return null; diff --git a/src/AasxMqttClient/MqttClient.cs b/src/AasxMqttClient/MqttClient.cs index 8747b9e0a..e0d01aab8 100644 --- a/src/AasxMqttClient/MqttClient.cs +++ b/src/AasxMqttClient/MqttClient.cs @@ -173,7 +173,7 @@ private string GenerateTopic(string template, } public async Task StartAsync( - AdminShellPackageEnv package, + AdminShellPackageEnvBase package, AnyUiDialogueDataMqttPublisher diaData, GrapevineLoggerToStoredPrints logger = null) { diff --git a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs index 744e1384e..2d8c19e2f 100644 --- a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs +++ b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs @@ -415,7 +415,7 @@ public void CommandBinding_CheckAndFix() // validate as XML var ms = new MemoryStream(); - PackageCentral.Main.SaveAs("noname.xml", true, AdminShellPackageEnv.SerializationFormat.Xml, ms, + PackageCentral.Main.SaveAs("noname.xml", true, AdminShellPackageFileBasedEnv.SerializationFormat.Xml, ms, saveOnlyCopy: true); ms.Flush(); ms.Position = 0; @@ -424,7 +424,7 @@ public void CommandBinding_CheckAndFix() // validate as JSON var ms2 = new MemoryStream(); - PackageCentral.Main.SaveAs("noname.json", true, AdminShellPackageEnv.SerializationFormat.Json, ms2, + PackageCentral.Main.SaveAs("noname.json", true, AdminShellPackageFileBasedEnv.SerializationFormat.Json, ms2, saveOnlyCopy: true); ms2.Flush(); ms2.Position = 0; @@ -530,7 +530,7 @@ public void CommandBinding_ConnectSecure() // start CONNECT as a worker (will start in the background) var worker = new BackgroundWorker(); - AdminShellPackageEnv envToload = null; + AdminShellPackageFileBasedEnv envToload = null; worker.DoWork += (s1, e1) => { for (int i = 0; i < 15; i++) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 8f213243d..54b63b13b 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -261,16 +261,16 @@ public void RestartUIafterNewPackage(bool onlyAuxiliary = false) } } - private AdminShellPackageEnv LoadPackageFromFile(string fn) + private AdminShellPackageFileBasedEnv LoadPackageFromFile(string fn) { if (fn.Trim().ToLower().EndsWith(".aml")) { - var res = new AdminShellPackageEnv(); + var res = new AdminShellPackageFileBasedEnv(); AasxAmlImExport.AmlImport.ImportInto(res, fn); return res; } else - return new AdminShellPackageEnv(fn, Options.Curr.IndirectLoadSave); + return new AdminShellPackageFileBasedEnv(fn, Options.Curr.IndirectLoadSave); } public void TakeOverContentEnable(bool enabled) @@ -366,7 +366,7 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() /// Index loaded contents, e.g. for animate of event sending public void UiLoadPackageWithNew( PackageCentralItem packItem, - AdminShellPackageEnv takeOverEnv = null, + AdminShellPackageFileBasedEnv takeOverEnv = null, string loadLocalFilename = null, string info = null, bool onlyAuxiliary = false, @@ -590,7 +590,7 @@ public void UiShowRepositories(bool visible) } public void PrepareDispEditEntity( - AdminShellPackageEnv package, ListOfVisualElementBasic entities, + AdminShellPackageEnvBase package, ListOfVisualElementBasic entities, bool editMode, bool hintMode, bool showIriMode, bool checkSmt, DispEditHighlight.HighlightFieldInfo hightlightField = null) { diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 1ed39b9a9..d4ecb6be4 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -39,7 +39,8 @@ // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_TechData_Work\\IDTA 02003-1-2_SubmodelTemplate_TechnicalData_v1.2__with_Draft_1_3_and_AsciiDoc_v01.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Draft_v22.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Examples_v08.aasx", - "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_v20_spiel.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_v20_spiel.aasx", + "AasxToLoad": "https://cloudrepo.aas-voyager.com/shells/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzY1NDNfNjAzMl8zMDEyXzU5NzM=", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\210_Copy_Paste\\Sample_AAS.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\310_Plugin_DNP\\Sample_AAS.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\200_Find_Replace\\Sample_SMT.aasx", @@ -136,18 +137,6 @@ ], "PluginPrefer": "ANYUI", "PluginDll": [ - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginUaNetServer\\bin\\Debug\\net6.0-windows\\AasxPluginUaNetServer.dll", - "Args": [ - "-single-nodeids", - "-single-keys", - "-ns", - "2", - "-ns", - "3" - ], - "Options": null - }, { "Path": "..\\..\\..\\..\\..\\..\\AasxPluginUaNetClient\\bin\\Debug\\net6.0-windows\\AasxPluginUaNetClient.dll", "Args": [] diff --git a/src/AasxPackageLogic/AasxPackageLogic.csproj b/src/AasxPackageLogic/AasxPackageLogic.csproj index 839bac01e..6598949f3 100644 --- a/src/AasxPackageLogic/AasxPackageLogic.csproj +++ b/src/AasxPackageLogic/AasxPackageLogic.csproj @@ -20,6 +20,7 @@ + @@ -27,6 +28,7 @@ PreserveNewest + diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 0d556cb02..8d2ec4576 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -2548,7 +2548,7 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, var tempFn = System.IO.Path.GetTempFileName().Replace(".tmp", ".txt"); System.IO.File.WriteAllText(tempFn, ""); - var mimeType = AdminShellPackageEnv.GuessMimeType(ptfn); + var mimeType = AdminShellPackageFileBasedEnv.GuessMimeType(ptfn); var targetPath = packages.Main.AddSupplementaryFileToStore( tempFn, ptd, ptfn, @@ -2705,7 +2705,7 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, var ptfn = System.IO.Path.GetFileName(uploadAssistance.SourcePath); packages.Main.PrepareSupplementaryFileParameters(ref ptd, ref ptfn); - var mimeType = AdminShellPackageEnv.GuessMimeType(ptfn); + var mimeType = AdminShellPackageFileBasedEnv.GuessMimeType(ptfn); var targetPath = packages.Main.AddSupplementaryFileToStore( uploadAssistance.SourcePath, ptd, ptfn, diff --git a/src/AasxPackageLogic/IMainWindow.cs b/src/AasxPackageLogic/IMainWindow.cs index 6b491b7a0..181f2d94b 100644 --- a/src/AasxPackageLogic/IMainWindow.cs +++ b/src/AasxPackageLogic/IMainWindow.cs @@ -118,7 +118,7 @@ void RedrawAllAasxElements( /// Index loaded contents, e.g. for animate of event sending void UiLoadPackageWithNew( PackageCentralItem packItem, - AdminShellPackageEnv takeOverEnv = null, + AdminShellPackageFileBasedEnv takeOverEnv = null, string loadLocalFilename = null, string info = null, bool onlyAuxiliary = false, diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 1dfd03bf5..397bac1e8 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -242,11 +242,11 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( try { // preferred format - var prefFmt = AdminShellPackageEnv.SerializationFormat.None; + var prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None; if (ucsf.FilterIndex == 1) - prefFmt = AdminShellPackageEnv.SerializationFormat.Xml; + prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.Xml; if (ucsf.FilterIndex == 2) - prefFmt = AdminShellPackageEnv.SerializationFormat.Json; + prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.Json; // save DisplayContextPlus.RememberForInitialDirectory(ucsf.TargetFileName); @@ -1257,7 +1257,7 @@ await DisplayContextPlus.CheckIfDownloadAndStart( // TODO (MIHO, 2022-11-17): not very elegant if (ticket.PostResults != null && ticket.PostResults.ContainsKey("TakeOver") - && ticket.PostResults["TakeOver"] is AdminShellPackageEnv pe) + && ticket.PostResults["TakeOver"] is AdminShellPackageFileBasedEnv pe) PackageCentral.MainItem.TakeOver(pe); MainWindow.RestartUIafterNewPackage(); diff --git a/src/AasxPackageLogic/MenuFunction/MenuFuncValidateSmt.cs b/src/AasxPackageLogic/MenuFunction/MenuFuncValidateSmt.cs index 0f74394fd..44b2aa4bb 100644 --- a/src/AasxPackageLogic/MenuFunction/MenuFuncValidateSmt.cs +++ b/src/AasxPackageLogic/MenuFunction/MenuFuncValidateSmt.cs @@ -255,7 +255,7 @@ private void CheckNonStructuralElement(IReferable rf) } public void PerformValidation( - Aas.Environment givenEnv = null, AdminShellPackageEnv package = null, string fn = null) + Aas.Environment givenEnv = null, AdminShellPackageEnvBase package = null, string fn = null) { // access Env = givenEnv; diff --git a/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/AasxFilePackageContainerBase.cs b/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/AasxFilePackageContainerBase.cs index f10bb1402..31d257b6e 100644 --- a/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/AasxFilePackageContainerBase.cs +++ b/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/AasxFilePackageContainerBase.cs @@ -19,7 +19,7 @@ public class AasxFilePackageContainerBase : PackageContainerBase public override async Task SaveToSourceAsync( string saveAsNewFileName = null, - AdminShellPackageEnv.SerializationFormat prefFmt = AdminShellPackageEnv.SerializationFormat.None, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None, PackCntRuntimeOptions runtimeOptions = null, bool doNotRememberLocation = false) { diff --git a/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/PackageContainerAasxFileRepository.cs b/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/PackageContainerAasxFileRepository.cs index c54c009f6..383b526d4 100644 --- a/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/PackageContainerAasxFileRepository.cs +++ b/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/PackageContainerAasxFileRepository.cs @@ -73,7 +73,7 @@ public async Task LoadAasxFileFromServer(string pa { var container = new AasxFilePackageContainerBase { - Env = new AdminShellPackageEnv(fileName, indirectLoadSave: false), + Env = new AdminShellPackageFileBasedEnv(fileName, indirectLoadSave: false), ContainerList = this, IsFormat = PackageContainerBase.Format.AASX, //TODO (jtikekar, 2022-04-04): Based on file PackageId = packageId @@ -125,7 +125,7 @@ public void LoadAasxFile(PackageCentral packageCentral, string fileName, int pac try { // load - var packageEnv = new AdminShellPackageEnv(fileName); + var packageEnv = new AdminShellPackageFileBasedEnv(fileName); // for each Admin Shell and then each Aas.AssetInformation var packageContainer = new PackageContainerRepoItem() diff --git a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs index 809efa1cc..0ee847ccc 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs @@ -111,7 +111,7 @@ public bool Load( } } - public bool TakeOver(AdminShellPackageEnv env) + public bool TakeOver(AdminShellPackageFileBasedEnv env) { try { @@ -165,7 +165,7 @@ public bool TakeOver(PackageContainerBase container) } public async Task SaveAsAsync(string saveAsNewFileName = null, - AdminShellPackageEnv.SerializationFormat prefFmt = AdminShellPackageEnv.SerializationFormat.None, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None, PackCntRuntimeOptions runtimeOptions = null, bool doNotRememberLocation = false) { @@ -250,12 +250,12 @@ public PackageCentralItem AuxItem get { return _aux; } } - public AdminShellPackageEnv Main + public AdminShellPackageEnvBase Main { get { return _main?.Container?.Env; } } - public AdminShellPackageEnv Aux + public AdminShellPackageEnvBase Aux { get { return _aux?.Container?.Env; } } @@ -307,7 +307,7 @@ public IEnumerable GetAllContainer(Func GetAllPackageEnv() + public IEnumerable GetAllPackageEnv() { if (_main?.Container?.Env != null) yield return _main?.Container.Env; @@ -320,7 +320,7 @@ public IEnumerable GetAllPackageEnv() yield return ri.Env; } - public IEnumerable GetAllPackageEnv(Func lambda) + public IEnumerable GetAllPackageEnv(Func lambda) { foreach (var pe in GetAllPackageEnv()) if (lambda == null || lambda.Invoke(pe)) diff --git a/src/AasxPackageLogic/PackageCentral/PackageConnectorBase.cs b/src/AasxPackageLogic/PackageCentral/PackageConnectorBase.cs index 706eb5347..241b42351 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageConnectorBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageConnectorBase.cs @@ -59,7 +59,7 @@ public class PackageConnectorBase : IPackageConnectorManageEvents /// /// Shortcut to PackageContainer's Environment. /// - public AdminShellPackageEnv Env { get { return _container?.Env; } } + public AdminShellPackageEnvBase Env { get { return _container?.Env; } } // // Constructors diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs index f35e6578e..72616f493 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs @@ -225,8 +225,8 @@ public PackCntChangeEventData(PackageContainerBase container, /// public class PackageContainerBase : IPackageConnectorManageEvents { - public enum Format { Unknown = 0, AASX, XML, JSON } - public static string[] FormatExt = { ".bin", ".aasx", ".xml", ".json" }; + public enum Format { Unknown = 0, AASX, XML, JSON, Elements } + public static string[] FormatExt = { ".bin", ".aasx", ".xml", ".json", ".unknown" }; public enum BackupType { XML = 0, FullCopy } @@ -234,7 +234,7 @@ public enum BackupType { XML = 0, FullCopy } public enum CopyMode { None = 0, Serialized = 1, BusinessData = 2 } [JsonIgnore] - public AdminShellPackageEnv Env = new AdminShellPackageEnv(); + public AdminShellPackageEnvBase Env = new AdminShellPackageFileBasedEnv(); [JsonIgnore] public Format IsFormat = Format.Unknown; @@ -267,7 +267,7 @@ public enum CopyMode { None = 0, Serialized = 1, BusinessData = 2 } public PackageContainerOptionsBase ContainerOptions = new PackageContainerOptionsBase(); /// - /// Links (optionally) to the ContainerList, which hold this Container. + /// Links (optionally) to the ContainerList, which holds this Container. /// To be set after adding to the list. /// [JsonIgnore] @@ -371,7 +371,7 @@ public virtual async Task SaveLocalCopyAsync( } public virtual async Task SaveToSourceAsync(string saveAsNewFileName = null, - AdminShellPackageEnv.SerializationFormat prefFmt = AdminShellPackageEnv.SerializationFormat.None, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None, PackCntRuntimeOptions runtimeOptions = null, bool doNotRememberLocation = false) { diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs index 0eec33709..3857a3eb3 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs @@ -70,6 +70,16 @@ public static PackageContainerGuess FromLocation( }; } + // has some API patterns indicating that it is part / element of a HTTP based repository + if (PackageContainerHttpRepoSubset.IsValidUriAnyMatch(location)) + { + return new PackageContainerGuess() + { + Location = location, + GuessedType = typeof(PackageContainerHttpRepoSubset) + }; + } + // starts with http ? if (ll.StartsWith("http://") || ll.StartsWith("https://")) { @@ -202,6 +212,15 @@ public static async Task GuessAndCreateForAsync( return cnt; } + if (guess.GuessedType == typeof(PackageContainerHttpRepoSubset)) + { + var cnt = await PackageContainerHttpRepoSubset.CreateAndLoadAsync( + packageCentral, location, fullItemLocation, + overrideLoadResident, takeOver: takeOver, + containerOptions: containerOptions, runtimeOptions: runtimeOptions); + return cnt; + } + // check FileInfo for (possible?) local file FileInfo fi = null; try diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs new file mode 100644 index 000000000..39e598bef --- /dev/null +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -0,0 +1,283 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + +using AasxIntegrationBase; +using AasxOpenIdClient; +using AdminShellNS; +using Aas = AasCore.Aas3_0; +using Extensions; +using IdentityModel.Client; +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace AasxPackageLogic.PackageCentral +{ + /// + /// This container represents a subset of AAS elements retrieved from a HTTP / networked repository. + /// + [DisplayName("HttpRepoSubset")] + public class PackageContainerHttpRepoSubset : PackageContainerRepoItem + { + /// + /// Location of the Container in a certain storage container, e.g. a local or network based + /// repository. In this implementation, the Location refers to a HTTP network ressource. + /// + public override string Location + { + get { return _location; } + set { SetNewLocation(value); OnPropertyChanged("InfoLocation"); } + } + // + // Constructors + // + + public PackageContainerHttpRepoSubset() + { + Init(); + } + + public PackageContainerHttpRepoSubset( + PackageCentral packageCentral, + string sourceLocation, PackageContainerOptionsBase containerOptions = null) + : base(packageCentral) + { + Init(); + SetNewLocation(sourceLocation); + if (containerOptions != null) + ContainerOptions = containerOptions; + } + + public PackageContainerHttpRepoSubset(CopyMode mode, PackageContainerBase other, + PackageCentral packageCentral = null, + string sourceLocation = null, PackageContainerOptionsBase containerOptions = null) + : base(mode, other, packageCentral) + { + if ((mode & CopyMode.Serialized) > 0 && other != null) + { + } + if ((mode & CopyMode.BusinessData) > 0 && other is PackageContainerNetworkHttpFile o) + { + sourceLocation = o.Location; + } + if (sourceLocation != null) + SetNewLocation(sourceLocation); + if (containerOptions != null) + ContainerOptions = containerOptions; + } + + + public static async Task CreateAndLoadAsync( + PackageCentral packageCentral, + string location, + string fullItemLocation, + bool overrideLoadResident, + PackageContainerBase takeOver = null, + PackageContainerListBase containerList = null, + PackageContainerOptionsBase containerOptions = null, + PackCntRuntimeOptions runtimeOptions = null) + { + var res = new PackageContainerHttpRepoSubset(CopyMode.Serialized, takeOver, + packageCentral, location, containerOptions); + + res.ContainerList = containerList; + + if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) + await res.LoadFromSourceAsync(fullItemLocation, runtimeOptions); + + return res; + } + + // + // Mechanics + // + + private void Init() + { + } + + private void SetNewLocation(string sourceUri) + { + _location = sourceUri; + IsFormat = Format.AASX; + } + + public override string ToString() + { + return "HTTP Repository element: " + Location; + } + + public static bool IsValidUriForAllAAS(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells(|/)$", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + + public static bool IsValidUriForSingleAAS(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells/(.{1,99})$", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + + public static bool IsValidUriForSingleSubmodel(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels/(.{1,99})$", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + if (m.Success) + return true; + + // TODO: Add AAS based Submodel + return false; + + } + + public static bool IsValidUriForSingleCD(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/conceptdescriptions/(.{1,99})$", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + + public static bool IsValidUriAnyMatch(string location) + { + return IsValidUriForAllAAS(location) + || IsValidUriForSingleAAS(location) + || IsValidUriForSingleSubmodel(location) + || IsValidUriForSingleCD(location); + } + + public override async Task LoadFromSourceAsync( + string fullItemLocation, + PackCntRuntimeOptions runtimeOptions = null) + { + //PackageHttpDownloadUtil.TryLoadFakeRequests(Assembly.GetExecutingAssembly(), + // "AasxPackageLogic.Resources.PackageContainerFakeAnswers.json"); + + var allowFakeResponses = true; + + var baseUri = PackageHttpDownloadUtil.GetBaseUri(fullItemLocation); + + // integrate in a fresh environment + // TODO: new kind of environment + var env = (Aas.IEnvironment) new Aas.Environment(); + + // start with AAS? + if (IsValidUriForSingleAAS(fullItemLocation)) + { + await PackageHttpDownloadUtil.DownloadToMemoryStream( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + lambdaDownloadDone: (ms) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + env.Add(Jsonization.Deserialize.AssetAdministrationShellFrom(node)); + } catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); + } + }); + } + + // start with Submodel? + if (IsValidUriForSingleSubmodel(fullItemLocation)) + { + await PackageHttpDownloadUtil.DownloadToMemoryStream( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + lambdaDownloadDone: (ms) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + env.Add(Jsonization.Deserialize.SubmodelFrom(node)); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded Submodel"); + } + }); + } + + // start with CD? + if (IsValidUriForSingleCD(fullItemLocation)) + { + await PackageHttpDownloadUtil.DownloadToMemoryStream( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + lambdaDownloadDone: (ms) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + env.Add(Jsonization.Deserialize.ConceptDescriptionFrom(node)); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded ConceptDescription"); + } + }); + } + + // start auto-load missing Submodels? + if (true) + foreach (var lr in env.FindAllSubmodelReferences(onlyNotExisting: true)) + await PackageHttpDownloadUtil.DownloadToMemoryStream( + sourceUri: PackageHttpDownloadUtil.BuildUriForSubmodel(baseUri, lr.Reference), + allowFakeResponses: allowFakeResponses, + lambdaDownloadDone: (ms) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + env.Add(Jsonization.Deserialize.SubmodelFrom(node)); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing auto-loaded Submodel"); + } + }); + + // prototypic!! + Env = new AdminShellPackageFileBasedEnv(); + Env.SetEnvironment(env); + } + + public override async Task SaveLocalCopyAsync( + string targetFilename, + PackCntRuntimeOptions runtimeOptions = null) + { + return false; + } + + private async Task UploadToServerAsync(string copyFn, Uri serverUri, + PackCntRuntimeOptions runtimeOptions = null) + { + } + + public override async Task SaveToSourceAsync(string saveAsNewFileName = null, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None, + PackCntRuntimeOptions runtimeOptions = null, + bool doNotRememberLocation = false) + { + } + } + +} diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs index cd34db524..25d864902 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs @@ -190,7 +190,7 @@ public void SaveAsLocalFile(string fn) } } - public void AddByAasPackage(PackageCentral packageCentral, AdminShellPackageEnv env, string fn) + public void AddByAasPackage(PackageCentral packageCentral, AdminShellPackageFileBasedEnv env, string fn) { // access if (env == null) @@ -219,7 +219,7 @@ public void AddByAasxFn(PackageCentral packageCentral, string fn) try { // load - var pkg = new AdminShellPackageEnv(fn); + var pkg = new AdminShellPackageFileBasedEnv(fn); // for each Admin Shell and then each AssetInformation this.AddByAasPackage(packageCentral, pkg, fn); @@ -243,7 +243,7 @@ public virtual void DeletePackageFromServer(PackageContainerRepoItem repoItem) // Converters & generators // - public void PopulateFakePackage(AdminShellPackageEnv pkg) + public void PopulateFakePackage(AdminShellPackageFileBasedEnv pkg) { // access if (pkg == null) diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs index 0d65b3fd8..0841b2115 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs @@ -143,7 +143,7 @@ public override async Task LoadFromSourceAsync( try { // TODO (MIHO, 2020-12-15): consider removing "indirectLoadSave" from AdminShellPackageEnv - Env = new AdminShellPackageEnv(fn, indirectLoadSave: false); + Env = new AdminShellPackageFileBasedEnv(fn, indirectLoadSave: false); } catch (Exception ex) { @@ -157,7 +157,7 @@ public override async Task LoadFromSourceAsync( public override async Task SaveToSourceAsync( string saveAsNewFileName = null, - AdminShellPackageEnv.SerializationFormat prefFmt = AdminShellPackageEnv.SerializationFormat.None, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None, PackCntRuntimeOptions runtimeOptions = null, bool doNotRememberLocation = false) { diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs index 635834bd3..d5a5e57ec 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs @@ -319,7 +319,7 @@ public override async Task LoadFromSourceAsync( // open try { - Env = new AdminShellPackageEnv(TempFn, indirectLoadSave: false); + Env = new AdminShellPackageFileBasedEnv(TempFn, indirectLoadSave: false); runtimeOptions?.Log?.Info($".. successfully opened as AASX environment: {Env?.AasEnv?.ToString()}"); } catch (Exception ex) @@ -445,7 +445,7 @@ private async Task UploadToServerAsync(string copyFn, Uri serverUri, } public override async Task SaveToSourceAsync(string saveAsNewFileName = null, - AdminShellPackageEnv.SerializationFormat prefFmt = AdminShellPackageEnv.SerializationFormat.None, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None, PackCntRuntimeOptions runtimeOptions = null, bool doNotRememberLocation = false) { diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs index 9ec909030..513fc76b0 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs @@ -243,7 +243,7 @@ public override async Task LoadFromSourceAsync( try { // TODO (MIHO, 2020-12-15): consider removing "indirectLoadSave" from AdminShellPackageEnv - Env = new AdminShellPackageEnv(fn, indirectLoadSave: false); + Env = new AdminShellPackageFileBasedEnv(fn, indirectLoadSave: false); } catch (Exception ex) { @@ -256,7 +256,7 @@ public override async Task LoadFromSourceAsync( } public override async Task SaveToSourceAsync(string saveAsNewFileName = null, - AdminShellPackageEnv.SerializationFormat prefFmt = AdminShellPackageEnv.SerializationFormat.None, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None, PackCntRuntimeOptions runtimeOptions = null, bool doNotRememberLocation = false) { diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs new file mode 100644 index 000000000..a83a9973d --- /dev/null +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -0,0 +1,343 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + +using AasxIntegrationBase.AdminShellEvents; +using AdminShellNS; +using Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Aas = AasCore.Aas3_0; +using AasxIntegrationBase; +using System.IO; +using AasxOpenIdClient; +using System.Net.Http; +using System.Net; +using System.Threading; +using IdentityModel.Client; +using System.Reflection; +using Newtonsoft.Json; +using static AasxPredefinedConcepts.ConceptModel.ConceptModelZveiTechnicalData; +using System.Text.RegularExpressions; + +namespace AasxPackageLogic.PackageCentral +{ + /// + /// This class provides an central download service for HTTP. + /// Can handle security. + /// Can handle fake request / responses. + /// + public class PackageHttpDownloadUtil + { + static PackageHttpDownloadUtil() + { + TryLoadFakeRequests(Assembly.GetExecutingAssembly(), + "AasxPackageLogic.Resources.PackageContainerFakeAnswers.json"); + } + + public class FakeReqResp + { + public string Request = ""; + public string Response = ""; + } + + public class FakeAnswers + { + public List Http = new List(); + } + + protected static FakeAnswers _fakeAnswers = new FakeAnswers(); + + public static void TryLoadFakeRequests(Assembly assy, string resPath) + { + // access resource + using (var stream = assy?.GetManifestResourceStream(resPath)) + using (StreamReader sr = new StreamReader(stream)) + using (JsonReader reader = new JsonTextReader(sr)) + { + JsonSerializer serializer = new JsonSerializer(); + _fakeAnswers = serializer.Deserialize(reader); + } + } + + private static OpenIdClientInstance.UiLambdaSet GenerateUiLambdaSet(PackCntRuntimeOptions runtimeOptions = null) + { + var res = new OpenIdClientInstance.UiLambdaSet(); + + if (runtimeOptions?.ShowMesssageBox != null) + res.MesssageBox = (content, text, title, buttons) => + runtimeOptions.ShowMesssageBox(content, text, title, buttons); + + return res; + } + + public static async Task DownloadToMemoryStream( + Uri sourceUri, + Action lambdaDownloadDone, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null, + bool allowFakeResponses = false) + { + // access + if (sourceUri == null) + return; + + // check for fake answers? + if (allowFakeResponses && _fakeAnswers?.Http != null) + foreach (var fa in _fakeAnswers.Http) + if (fa.Request.Equals(sourceUri.ToString(), StringComparison.InvariantCultureIgnoreCase)) + { + using (var memStream = new MemoryStream()) + using (var writer = new StreamWriter(memStream)) + { + writer.Write(AdminShellUtil.Base64Decode(fa.Response)); + writer.Flush(); + memStream.Flush(); + memStream.Seek(0, SeekOrigin.Begin); + lambdaDownloadDone?.Invoke(memStream); + return; + } + } + + // TODO: debug, remove!! + return; + + // read via HttpClient (uses standard proxies) + var handler = new HttpClientHandler(); + handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; + handler.AllowAutoRedirect = false; + + var client = new HttpClient(handler); + + client.DefaultRequestHeaders.Add("Accept", "application/aas"); + client.BaseAddress = new Uri(sourceUri.GetLeftPart(UriPartial.Authority)); + var requestPath = sourceUri.PathAndQuery; + + // Log + runtimeOptions?.Log?.Info($"HttpClient() with base-address {client.BaseAddress} " + + $"and request {requestPath} .. "); + + // Token existing? + var clhttp = containerList as PackageContainerListHttpRestBase; + var oidc = clhttp?.OpenIdClient; + if (oidc == null) + { + runtimeOptions?.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (clhttp != null && OpenIDClient.email != "") + { + clhttp.OpenIdClient = new OpenIdClientInstance(); + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + oidc = clhttp.OpenIdClient; + } + } + if (oidc != null) + { + if (oidc.token != "") + { + runtimeOptions?.Log?.Info($" using existing bearer token."); + client.SetBearerToken(oidc.token); + } + else + { + if (oidc.email != "") + { + runtimeOptions?.Log?.Info($" using existing email token."); + client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); + } + } + } + + bool repeat = true; + + while (repeat) + { + // get response? + var response = await client.GetAsync(requestPath, + HttpCompletionOption.ResponseHeadersRead); + + if (clhttp != null + && response.StatusCode == System.Net.HttpStatusCode.TemporaryRedirect) + { + string redirectUrl = response.Headers.Location.ToString(); + // ReSharper disable once RedundantExplicitArrayCreation + string[] splitResult = redirectUrl.Split(new string[] { "?" }, + StringSplitOptions.RemoveEmptyEntries); + splitResult[0] = splitResult[0].TrimEnd('/'); + + if (splitResult.Length < 1) + { + runtimeOptions?.Log?.Error("TemporaryRedirect, but url split to successful"); + break; + } + + runtimeOptions?.Log?.Info("Redirect to: " + splitResult[0]); + + if (oidc == null) + { + runtimeOptions?.Log?.Info("Creating new OpenIdClient.."); + oidc = new OpenIdClientInstance(); + clhttp.OpenIdClient = oidc; + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + } + + oidc.authServer = splitResult[0]; + + runtimeOptions?.Log?.Info($".. authentication at auth server {oidc.authServer} needed"); + + var response2 = await oidc.RequestTokenAsync(null, + GenerateUiLambdaSet(runtimeOptions)); + if (oidc.keycloak == "" && response2 != null) + oidc.token = response2.AccessToken; + if (oidc.token != "" && oidc.token != null) + client.SetBearerToken(oidc.token); + + repeat = true; + continue; + } + + repeat = false; + + if (response.IsSuccessStatusCode) + { + var contentLength = response.Content.Headers.ContentLength; + var contentFn = response.Content.Headers.ContentDisposition?.FileName; + + // log + runtimeOptions?.Log?.Info($".. response with header-content-len {contentLength} " + + $"and file-name {contentFn} .."); + + var contentStream = await response?.Content?.ReadAsStreamAsync(); + if (contentStream == null) + throw new PackageContainerException( + $"While getting data bytes from {sourceUri.ToString()} via HttpClient " + + $"no data-content was responded!"); + + // create temp file and write to it + var givenFn = sourceUri.ToString(); + if (contentFn != null) + givenFn = contentFn; + runtimeOptions?.Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " + + $"and request {requestPath} .. "); + + using (var memStream = new MemoryStream()) + { + // copy with progress + var bufferSize = 4024; + var deltaSize = 512 * 1024; + var buffer = new byte[bufferSize]; + long totalBytesRead = 0; + long lastBytesRead = 0; + int bytesRead; + + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Starting, + contentLength, totalBytesRead); + + while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, + default(CancellationToken)).ConfigureAwait(false)) != 0) + { + await memStream.WriteAsync(buffer, 0, bytesRead, + default(CancellationToken)).ConfigureAwait(false); + + totalBytesRead += bytesRead; + + if (totalBytesRead > lastBytesRead + deltaSize) + { + runtimeOptions?.Log?.Info($".. downloading to memory stream"); + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Ongoing, + contentLength, totalBytesRead); + lastBytesRead = totalBytesRead; + } + } + + // assume bytes read to be total bytes + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Final, + totalBytesRead, totalBytesRead); + + // log + runtimeOptions?.Log?.Info($".. download done with {totalBytesRead} bytes read!"); + + // execute lambda + memStream.Flush(); + memStream.Seek(0, SeekOrigin.Begin); + lambdaDownloadDone?.Invoke(memStream); + } + } + else + { + Log.Singleton.Error("DownloadFromSource Server gave: Operation not allowed!"); + throw new PackageContainerException($"Server operation not allowed!"); + } + } + } + + public static Uri GetBaseUri(string location) + { + // try an explicit search for known parts of ressources + // (preserves scheme, host and leading pathes) + var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/conceptdescription)"); + if (m.Success) + return new Uri(m.Groups[1].ToString() + "/"); + + // just go to the first slash + var p0 = location.IndexOf("//"); + if (p0 > 0) + { + var p = location.IndexOf('/', p0 + 2); + if (p > 0) + { + return new Uri(location.Substring(0, p) + "/"); + } + } + + // go to error + return null; + } + + //public static string CombineUri (string uri1, string uri2) + //{ + // var res = "" + uri1; + // if (uri2?.HasContent() == true) + // { + // if (!res.EndsWith("/")) + // res += "/"; + // res += uri2; + // } + // return res; + //} + + public static Uri CombineUri(Uri baseUri, string relativeUri) + { + if (baseUri == null || relativeUri?.HasContent() != true) + return null; + + if (Uri.TryCreate(baseUri, relativeUri, out var res)) + return res; + + return null; + } + + public static Uri BuildUriForSubmodel(Uri baseUri, Aas.IReference submodelRef) + { + // access + if (baseUri == null || submodelRef?.IsValid() != true + || submodelRef.Count() != 1 || submodelRef.Keys[0].Type != KeyTypes.Submodel) + return null; + + // try combine + var smidenc = AdminShellUtil.Base64Encode(submodelRef.Keys[0].Value); + return CombineUri(baseUri, $"submodels/{smidenc}"); + } + + } +} diff --git a/src/AasxPackageLogic/Resources/PackageContainerFakeAnswers.json b/src/AasxPackageLogic/Resources/PackageContainerFakeAnswers.json new file mode 100644 index 000000000..0d0e1f049 --- /dev/null +++ b/src/AasxPackageLogic/Resources/PackageContainerFakeAnswers.json @@ -0,0 +1,19 @@ +{ + "Http": [ + // All AAS + { + "Request": "https://cloudrepo.aas-voyager.com/shells", + "Response": "eyJyZXN1bHQiOlt7ImlkU2hvcnQiOiJteUFBU3dpdGhHbG9iYWxTZWN1cml0eU1ldGFNb2RlbCIsImlkIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzY1NDNfNjAzMl8zMDEyXzU5NzMiLCJhc3NldEluZm9ybWF0aW9uIjp7ImFzc2V0S2luZCI6Ikluc3RhbmNlIiwiZ2xvYmFsQXNzZXRJZCI6Ind3dy5jb21wYW55LmNvbS9pZHMvYXNzZXQvMDE2NF85MDIyXzExOTFfNzM2MiJ9LCJzdWJtb2RlbHMiOlt7InR5cGUiOiJNb2RlbFJlZmVyZW5jZSIsImtleXMiOlt7InR5cGUiOiJTdWJtb2RlbCIsInZhbHVlIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NSJ9XX0seyJ0eXBlIjoiTW9kZWxSZWZlcmVuY2UiLCJrZXlzIjpbeyJ0eXBlIjoiU3VibW9kZWwiLCJ2YWx1ZSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vaWRzL3NtLzU0MzFfMjE4MV8zMDEyXzM5MjkifV19XSwibW9kZWxUeXBlIjoiQXNzZXRBZG1pbmlzdHJhdGlvblNoZWxsIn0seyJpZFNob3J0IjoiUkVHSVNUUlkiLCJpZCI6IkFzc2V0QWRtaW5pc3RyYXRpb25TaGVsbC0tLTAxMkY0NkFGIiwiYXNzZXRJbmZvcm1hdGlvbiI6eyJhc3NldEtpbmQiOiJJbnN0YW5jZSIsImdsb2JhbEFzc2V0SWQiOiJBc3NldC0tLTA2QTlGRTRGIn0sInN1Ym1vZGVscyI6W3sidHlwZSI6Ik1vZGVsUmVmZXJlbmNlIiwia2V5cyI6W3sidHlwZSI6IlN1Ym1vZGVsIiwidmFsdWUiOiJodHRwczovL2V4YW1wbGUuY29tL2lkcy9zbS84MjAyXzYwMTFfMzAyMl8xNjIwIn1dfSx7InR5cGUiOiJNb2RlbFJlZmVyZW5jZSIsImtleXMiOlt7InR5cGUiOiJTdWJtb2RlbCIsInZhbHVlIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMDE1NF85MDkyXzExMjJfMjIyMCJ9XX1dLCJtb2RlbFR5cGUiOiJBc3NldEFkbWluaXN0cmF0aW9uU2hlbGwifV0sInBhZ2luZ19tZXRhZGF0YSI6e319" + }, + // Single AAS + { + "Request": "https://cloudrepo.aas-voyager.com/shells/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzY1NDNfNjAzMl8zMDEyXzU5NzM=", + "Response": "eyJpZFNob3J0IjoibXlBQVN3aXRoR2xvYmFsU2VjdXJpdHlNZXRhTW9kZWwiLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5jb20vaWRzL2Fhcy82NTQzXzYwMzJfMzAxMl81OTczIiwiYXNzZXRJbmZvcm1hdGlvbiI6eyJhc3NldEtpbmQiOiJJbnN0YW5jZSIsImdsb2JhbEFzc2V0SWQiOiJ3d3cuY29tcGFueS5jb20vaWRzL2Fzc2V0LzAxNjRfOTAyMl8xMTkxXzczNjIifSwic3VibW9kZWxzIjpbeyJ0eXBlIjoiTW9kZWxSZWZlcmVuY2UiLCJrZXlzIjpbeyJ0eXBlIjoiU3VibW9kZWwiLCJ2YWx1ZSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vaWRzL3NtLzIwMTVfNjAyMF8zMDEyXzA1ODUifV19LHsidHlwZSI6Ik1vZGVsUmVmZXJlbmNlIiwia2V5cyI6W3sidHlwZSI6IlN1Ym1vZGVsIiwidmFsdWUiOiJodHRwczovL2V4YW1wbGUuY29tL2lkcy9zbS81NDMxXzIxODFfMzAxMl8zOTI5In1dfV0sIm1vZGVsVHlwZSI6IkFzc2V0QWRtaW5pc3RyYXRpb25TaGVsbCJ9" + }, + // Single Submodel + { + "Request": "https://cloudrepo.aas-voyager.com/submodels/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ==", + "Response": "ICB7CiAgICAiaWRTaG9ydCI6ICJOYW1lcGxhdGUiLAogICAgImRlc2NyaXB0aW9uIjogWwogICAgICB7CiAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAidGV4dCI6ICJDb250YWlucyB0aGUgbmFtZXBsYXRlIGluZm9ybWF0aW9uIGF0dGFjaGVkIHRvIHRoZSBwcm9kdWN0IgogICAgICB9CiAgICBdLAogICAgImFkbWluaXN0cmF0aW9uIjogewogICAgICAidmVyc2lvbiI6ICIyIiwKICAgICAgInJldmlzaW9uIjogIjAiCiAgICB9LAogICAgImlkIjogImh0dHBzOi8vc21hcnQuZmVzdG8uY29tL3NtLzAwMS8wNjBmZjY0Zi05ZmQyLTQyMmQtODFjZS1iMTdlNDlmMDA3YzUvIiwKICAgICJraW5kIjogIkluc3RhbmNlIiwKICAgICJzZW1hbnRpY0lkIjogewogICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICJrZXlzIjogWwogICAgICAgIHsKICAgICAgICAgICJ0eXBlIjogIlN1Ym1vZGVsIiwKICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgInN1Ym1vZGVsRWxlbWVudHMiOiBbCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJVUklPZlRoZVByb2R1Y3QiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFZODExIzAwMSIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICJ2YWx1ZSI6ICIiLAogICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJOYW1lIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzY3NyMwMDIiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgInRleHQiOiAiRkVTVE8gU0UgXHUwMDI2IENvLiBLRyIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIkZFU1RPIFNFIFx1MDAyNiBDby4gS0ciCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIk1hbnVmYWN0dXJlclByb2R1Y3REZXNpZ25hdGlvbiIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVczMzgjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICJ0ZXh0IjogIkVpbnppZ2FydGlnIGZsZXhpYmVsIGltIEFuc2NobHVzczogZGVyIERydWNrc2Vuc29yIFNQQVUuIE9iIERydWNrbWVzc3VuZywgRHJ1Y2tcdTAwRkNiZXJ3YWNodW5nIG9kZXIgRHJ1Y2thYmZyYWdlIFx1MjAxMyBTaWUgaGFiZW4gYWxsZSBEcnVja3dlcnRlIGltbWVyIGF1ZiBlaW5lbiBCbGljayB1bnRlciBLb250cm9sbGUuIEltIElPLUxpbmstTW9kZSBzaW5kIEZlcm53YXJ0dW5nIHVuZCAtcGFyYW1ldHJpZXJ1bmcgc293aWUgZWluZSBlaW5mYWNoZSBTZW5zb3JlbnJlcGxpemllcnVuZyBtXHUwMEY2Z2xpY2guIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgInRleHQiOiAiVGhlIHByZXNzdXJlIHNlbnNvciBTUEFVIGhhcyBhIHVuaXF1ZWx5IGZsZXhpYmxlIGNvbm5lY3Rpb24uIFdoZXRoZXIgZm9yIHByZXNzdXJlIG1lYXN1cmluZywgcHJlc3N1cmUgbW9uaXRvcmluZyBvciBwcmVzc3VyZSBzZW5zaW5nLCBhbGwgcHJlc3N1cmUgdmFsdWVzIGFyZSBhbHdheXMgdW5kZXIgY29udHJvbCBhdCBhIGdsYW5jZS4gUmVtb3RlIG1haW50ZW5hbmNlIGFuZCBwYXJhbWV0ZXJpc2F0aW9uIGFzIHdlbGwgYXMgc2ltcGxlIHNlbnNvciByZXBsaWNhdGlvbiBhcmUgcG9zc2libGUgaW4gSU8tTGlua1x1MDBBRSBtb2RlLiIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiTWFudWZhY3R1cmVyUHJvZHVjdFJvb3QiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFVNzMyIzAwMSIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAidGV4dCI6ICJEcnVja3NlbnNvciIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIlByZXNzdXJlIHNlbnNvciIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiTWFudWZhY3R1cmVyUHJvZHVjdEZhbWlseSIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVU3MzEjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICJ0ZXh0IjogIlNQQVUiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAidGV4dCI6ICJTUEFVIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJQcm9kdWN0VHlwZSIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8wNTcjMDAyIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICJ0ZXh0IjogIlNQQVUtUDEwUi1ULVIxOE0tTC1QTkxLLVBOVkJBLU04RCIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIlNQQVUtUDEwUi1ULVIxOE0tTC1QTkxLLVBOVkJBLU04RCIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiT3JkZXJDb2RlT2ZNYW51ZmFjdHVyZXIiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMjI3IzAwMiIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAidGV4dCI6ICJTUEFVLVAxMFItVC1SMThNLUwtUE5MSy1QTlZCQS1NOEQiCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIlByb2R1Y3RBcnRpY2xlTnVtYmVyT2ZNYW51ZmFjdHVyZXIiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPNjc2IzAwMyIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAidGV4dCI6ICI4MDAxMjAzIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJTZXJpYWxOdW1iZXIiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFNNTU2IzAwMiIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICJ2YWx1ZSI6ICIiLAogICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJZZWFyT2ZDb25zdHJ1Y3Rpb24iLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFQOTA2IzAwMSIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICJ2YWx1ZSI6ICIyMDIzIiwKICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiQ291bnRyeU9mT3JpZ2luIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzI1OSMwMDQiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAidmFsdWUiOiAiREUiLAogICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJDb21wYW55TG9nbyIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZS9Db21wYW55TG9nbyAiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6ICIvYWFzeC9OYW1lcGxhdGUvRmVzdG9Mb2dvLnBuZyIsCiAgICAgICAgImNvbnRlbnRUeXBlIjogImltYWdlL3BuZy8iLAogICAgICAgICJtb2RlbFR5cGUiOiAiRmlsZSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIkNvbnRhY3RJbmZvcm1hdGlvbiIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMS8wL0NvbnRhY3RJbmZvcm1hdGlvbnMvQ29udGFjdEluZm9ybWF0aW9uIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIkRlcGFydG1lbnQiLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTI3IzAwMyIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAgICAgInRleHQiOiAiS29udGFrdCB6dSBGZXN0byIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJDb250YWN0IHRvIEZlc3RvIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJTdHJlZXQiLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTI4IzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAgICAgInRleHQiOiAiUnVpdGVyIFN0cmFcdTAwREZlIDgyIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIlJ1aXRlciBTdHJhXHUwMERGZSA4MiIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiWmlwY29kZSIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMjkjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICI3MzczNCIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICI3MzczNCIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiUE9Cb3giLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTMwIzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiWmlwQ29kZU9mUE9Cb3giLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTMxIzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiQ2l0eVRvd24iLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTMyIzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAgICAgInRleHQiOiAiRXNzbGluZ2VuIGFtIE5lY2thciIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJFc3NsaW5nZW4gYW0gTmVja2FyIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJTdGF0ZUNvdW50eSIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMzMjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJCYWRlbi1XdWVydHRlbWJlcmciCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAgICAgInRleHQiOiAiQmFkZW4tV1x1MDBGQ3J0dGVtYmVyZyIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiTmF0aW9uYWxDb2RlIiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzEzNCMwMDIiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIkRFIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIkRFIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJBZGRyZXNzT2ZBZGRpdGlvbmFsTGluayIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVEzMjYjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAidmFsdWUiOiAid3d3LmZlc3RvLmNvbS9jb250YWN0IiwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIlBob25lIiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8xLzAvQ29udGFjdEluZm9ybWF0aW9ucy9Db250YWN0SW5mb3JtYXRpb24vUGhvbmUiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiVGVsZXBob25lTnVtYmVyIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzEzNiMwMDIiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgICAgICAgICAidGV4dCI6ICJcdTAwMkI0OSAoMCkgNzExIDM0NyAwIgogICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICAgICAidGV4dCI6ICJcdTAwMkI0OSAoMCkgNzExIDM0NyAwIgogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJUeXBlT2ZUZWxlcGhvbmUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTM3IzAwMyIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAib2ZmaWNlIiwKICAgICAgICAgICAgICAgICJ2YWx1ZUlkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzA3LUFBUzc1NCMwMDEiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIkZheCIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVE4MzQjMDA1IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIkZheE51bWJlciIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xOTUjMDAyIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAgICAgInRleHQiOiAiXHUwMDJCNDkgKDApIDcxMSAzNDcgMjE0NCIKICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAgICAgInRleHQiOiAiXHUwMDJCNDkgKDApIDcxMSAzNDcgMjE0NCIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiVHlwZU9mRmF4TnVtYmVyIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzE5NiMwMDMiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIm9mZmljZSIsCiAgICAgICAgICAgICAgICAidmFsdWVJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwNy1BQVM3NTQjMDAxIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5ncyIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BR1o2NzMjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmcwMSIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BSEQyMDYjMDAxIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdOYW1lIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ05hbWUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIm5hY2ggRVUtRU1WLVJpY2h0bGluaWUiLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdGaWxlIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ0ZpbGUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlIjogIi9hYXN4L05hbWVwbGF0ZS9DRV9NYXJraW5nXzIwMTYucG5nIiwKICAgICAgICAgICAgICAgICJjb250ZW50VHlwZSI6ICJhcHBsaWNhdGlvbi9wbmciLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJGaWxlIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0FkZGl0aW9uYWxUZXh0IiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ0FkZGl0aW9uYWxUZXh0IgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJhY2NvcmRpbmcgdG8gRU1DIGRpcmVjdGl2ZSAyMDE0LzMwL0VVOyBpbmNsdWRlcyBFVS1Sb0hTIiwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nMDIiLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDEtQUhEMjA2IzAwMSIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nTmFtZSIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZS9NYXJraW5ncy9NYXJraW5nL01hcmtpbmdOYW1lIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJuYWNoIEVVLVJvSFMtUmljaHRsaW5pZSIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0ZpbGUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nRmlsZSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2Fhc3gvTmFtZXBsYXRlL2NoaW5hLVJvSFMtdHJhbnNwYXJlbnQucG5nIiwKICAgICAgICAgICAgICAgICJjb250ZW50VHlwZSI6ICJhcHBsaWNhdGlvbi9wbmciLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJGaWxlIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0FkZGl0aW9uYWxUZXh0IiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ0FkZGl0aW9uYWxUZXh0IgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJmb3IgQ2hpbmEiLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmcwMyIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BSEQyMDYjMDAxIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdOYW1lIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ05hbWUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIlJDTSBNYXJrIiwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nRmlsZSIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZS9NYXJraW5ncy9NYXJraW5nL01hcmtpbmdGaWxlIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIvYWFzeC9OYW1lcGxhdGUvUkNNLnBuZyIsCiAgICAgICAgICAgICAgICAiY29udGVudFR5cGUiOiAiYXBwbGljYXRpb24vcG5nIiwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiRmlsZSIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmcwNCIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BSEQyMDYjMDAxIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdOYW1lIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ05hbWUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogImMgVUwgdXMgLSBMaXN0ZWQgKE9MKSIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0ZpbGUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nRmlsZSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2Fhc3gvTmFtZXBsYXRlL2MtdWwtdXMtdHJhbnNwYXJlbnQucG5nIiwKICAgICAgICAgICAgICAgICJjb250ZW50VHlwZSI6ICJhcHBsaWNhdGlvbi9wbmciLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJGaWxlIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgogICAgICB9CiAgICBdLAogICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbCIKICB9" + } + ] +} \ No newline at end of file diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 49eb431d8..326f032ac 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -590,14 +590,14 @@ public enum ConceptDescSortOrder { } public string thePackageSourceFn; - public AdminShellPackageEnv thePackage = null; + public AdminShellPackageEnvBase thePackage = null; public Aas.IEnvironment theEnv = null; public ItemType theItemType = ItemType.Env; private object _mainDataObject; private static ConceptDescSortOrder _cdSortOrder = ConceptDescSortOrder.None; public VisualElementEnvironmentItem( - VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnv package, + VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnvBase package, Aas.IEnvironment env, ItemType itemType, string packageSourceFn = null, object mainDataObject = null) @@ -687,12 +687,12 @@ public static void SetCdSortOrderByString(string order) public class VisualElementAdminShell : VisualElementGeneric { - public AdminShellPackageEnv thePackage = null; + public AdminShellPackageEnvBase thePackage = null; public Aas.IEnvironment theEnv = null; public Aas.IAssetAdministrationShell theAas = null; public VisualElementAdminShell( - VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnv package, + VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnvBase package, Aas.IEnvironment env, Aas.IAssetAdministrationShell aas) : base() { @@ -787,14 +787,14 @@ public override void RefreshFromMainData() public class VisualElementSubmodelRef : VisualElementGeneric { public Aas.IEnvironment theEnv = null; - public AdminShellPackageEnv thePackage = null; + public AdminShellPackageEnvBase thePackage = null; public Aas.IAssetAdministrationShell theAas = null; public Aas.IReference theSubmodelRef = null; public Aas.ISubmodel theSubmodel = null; public VisualElementSubmodelRef( VisualElementGeneric parent, TreeViewLineCache cache, Aas.IEnvironment env, - AdminShellPackageEnv package, + AdminShellPackageEnvBase package, Aas.IAssetAdministrationShell aas, Aas.IReference smr, Aas.ISubmodel sm) : base() @@ -1475,11 +1475,11 @@ public override void RefreshFromMainData() public class VisualElementSupplementalFile : VisualElementGeneric { - public AdminShellPackageEnv thePackage = null; + public AdminShellPackageEnvBase thePackage = null; public AdminShellPackageSupplementaryFile theFile = null; public VisualElementSupplementalFile( - VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnv package, + VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnvBase package, AdminShellPackageSupplementaryFile sf) : base() { @@ -1532,7 +1532,7 @@ public override void RefreshFromMainData() public class VisualElementPluginExtension : VisualElementGeneric { - public AdminShellPackageEnv thePackage = null; + public AdminShellPackageEnvBase thePackage = null; public Aas.IReferable theReferable = null; public Plugins.PluginInstance thePlugin = null; @@ -1541,7 +1541,7 @@ public class VisualElementPluginExtension : VisualElementGeneric public VisualElementPluginExtension( VisualElementGeneric parent, TreeViewLineCache cache, - AdminShellPackageEnv package, + AdminShellPackageEnvBase package, Aas.IReferable referable, Plugins.PluginInstance plugin, AasxIntegrationBase.AasxPluginResultVisualExtension ext) @@ -1834,7 +1834,7 @@ private VisualElementGeneric GenerateVisualElementsFromShellEnvAddElements( } private void GenerateInnerElementsForSubmodelRef( - TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnv package, + TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package, Aas.ISubmodel sm, VisualElementSubmodelRef tiSm) { @@ -1877,7 +1877,7 @@ private VisualElementSubmodelRef GenerateVisuElemForVisualElementSubmodelRef( Aas.ISubmodel sm, Aas.IReference smr, VisualElementGeneric parent, - TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnv package = null) + TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package = null) { // trivial if (smr == null || sm == null) @@ -1904,7 +1904,7 @@ private VisualElementSubmodelRef GenerateVisuElemForVisualElementSubmodelRef( private VisualElementAdminShell GenerateVisuElemForAAS( Aas.IAssetAdministrationShell aas, - TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnv package = null, + TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package = null, bool editMode = false) { // trivial @@ -2205,7 +2205,7 @@ private void GenerateInnerElementsForConceptDescriptions( } public void AddVisualElementsFromShellEnv( - TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnv package = null, + TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package = null, string packageSourceFn = null, bool editMode = false, int expandMode = 0, bool lazyLoadingFirst = false) { @@ -2425,7 +2425,7 @@ public void AddVisualElementsFromShellEnv( } private void SetElementToLazyLoading( - TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnv package, + TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package, VisualElementGeneric parent) { var tiDummy = new VisualElementEnvironmentItem(parent, cache, package, env, diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs index a9678dc28..a1a4756fd 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs @@ -34,7 +34,7 @@ public class AssetInterfaceAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private AssetInterfaceOptions _options = null; private PluginEventStack _eventStack = null; @@ -85,7 +85,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, AssetInterfaceOptions theOptions, PluginEventStack eventStack, @@ -142,7 +142,7 @@ public static AssetInterfaceAnyUiControl FillWithAnyUiControls( AidAllInterfaceStatus ifxStatus) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -166,7 +166,7 @@ public static AssetInterfaceAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // test trivial access @@ -186,7 +186,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, IEnumerable foundRecs, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // make an outer grid, very simple grid of two rows: header & body @@ -268,7 +268,7 @@ protected AnyUiLambdaActionBase TriggerUpdate(bool full = true) protected void RenderPanelInner( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, AssetInterfaceOptionsRecord rec, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // access diff --git a/src/AasxPluginBomStructure/GenericBomControl.cs b/src/AasxPluginBomStructure/GenericBomControl.cs index bdf2c3c73..da0c4979f 100644 --- a/src/AasxPluginBomStructure/GenericBomControl.cs +++ b/src/AasxPluginBomStructure/GenericBomControl.cs @@ -36,7 +36,7 @@ namespace AasxPluginBomStructure /// public class GenericBomControl { - private AdminShellPackageEnv _package; + private AdminShellPackageFileBasedEnv _package; private Aas.Submodel _submodel; private bool _createOnPackage = false; @@ -244,7 +244,7 @@ public object FillWithWpfControls( object opackage, object osm, object masterDockPanel) { // access - _package = opackage as AdminShellPackageEnv; + _package = opackage as AdminShellPackageFileBasedEnv; _submodel = osm as Aas.Submodel; _createOnPackage = false; _bomOptions = bomOptions; @@ -979,7 +979,7 @@ public object CreateViewPackageReleations( DockPanel master) { // access - _package = opackage as AdminShellPackageEnv; + _package = opackage as AdminShellPackageFileBasedEnv; _submodel = null; _createOnPackage = true; _bomOptions = bomOptions; @@ -1047,7 +1047,7 @@ public object CreateViewPackageReleations( } private Microsoft.Msagl.Drawing.Graph CreateGraph( - AdminShellPackageEnv env, + AdminShellPackageFileBasedEnv env, Aas.Submodel sm, GenericBomCreatorOptions options, bool createOnPackage = false) diff --git a/src/AasxPluginContactInformation/ContactEntity.cs b/src/AasxPluginContactInformation/ContactEntity.cs index 4e7738ae4..cb0e8a644 100644 --- a/src/AasxPluginContactInformation/ContactEntity.cs +++ b/src/AasxPluginContactInformation/ContactEntity.cs @@ -117,7 +117,7 @@ public class ListOfContactEntity : List // public static ListOfContactEntity ParseSubmodelForV10( - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel subModel, AasxPluginContactInformation.ContactInformationOptions options, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) diff --git a/src/AasxPluginContactInformation/ContactListAnyUiControl.cs b/src/AasxPluginContactInformation/ContactListAnyUiControl.cs index 94bf2a247..f3e120af0 100644 --- a/src/AasxPluginContactInformation/ContactListAnyUiControl.cs +++ b/src/AasxPluginContactInformation/ContactListAnyUiControl.cs @@ -35,7 +35,7 @@ public class ShelfAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private ContactInformationOptions _options = null; private PluginEventStack _eventStack = null; @@ -80,7 +80,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, ContactInformationOptions theOptions, PluginEventStack eventStack, @@ -120,7 +120,7 @@ public static ShelfAnyUiControl FillWithAnyUiControls( AasxPluginBase plugin) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) diff --git a/src/AasxPluginDigitalNameplate/NameplateAnyUiControl.cs b/src/AasxPluginDigitalNameplate/NameplateAnyUiControl.cs index 6dfe9ee05..83f9570e3 100644 --- a/src/AasxPluginDigitalNameplate/NameplateAnyUiControl.cs +++ b/src/AasxPluginDigitalNameplate/NameplateAnyUiControl.cs @@ -40,7 +40,7 @@ public class NameplateAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private DigitalNameplateOptions _options = null; private PluginEventStack _eventStack = null; @@ -75,7 +75,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, DigitalNameplateOptions theOptions, PluginEventStack eventStack, @@ -112,7 +112,7 @@ public static NameplateAnyUiControl FillWithAnyUiControls( AasxPluginBase plugin) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) diff --git a/src/AasxPluginDigitalNameplate/NameplateData.cs b/src/AasxPluginDigitalNameplate/NameplateData.cs index fddf63382..eefbc2c0e 100644 --- a/src/AasxPluginDigitalNameplate/NameplateData.cs +++ b/src/AasxPluginDigitalNameplate/NameplateData.cs @@ -63,7 +63,7 @@ public NameplateData() { } // public static string TryGuessAssetId( - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel subModel) { // access @@ -82,7 +82,7 @@ public static string TryGuessAssetId( // public static NameplateData ParseSubmodelForV10( - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel subModel, DigitalNameplateOptions options, string defaultLang = null) { @@ -205,7 +205,7 @@ public static NameplateData ParseSubmodelForV10( // public static NameplateData ParseSubmodelForV20( - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel subModel, DigitalNameplateOptions options, string defaultLang = null) { diff --git a/src/AasxPluginDocumentShelf/DocumentEntity.cs b/src/AasxPluginDocumentShelf/DocumentEntity.cs index 8e2bd52a4..9d5db49d5 100644 --- a/src/AasxPluginDocumentShelf/DocumentEntity.cs +++ b/src/AasxPluginDocumentShelf/DocumentEntity.cs @@ -173,7 +173,7 @@ public class ListOfDocumentEntity : List // public static ListOfDocumentEntity ParseSubmodelForV10( - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel subModel, AasxPluginDocumentShelf.DocumentShelfOptions options, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) @@ -345,7 +345,7 @@ private static void SearchForRelations( } public static ListOfDocumentEntity ParseSubmodelForV11( - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel subModel, AasxPredefinedConcepts.VDI2770v11 defs11, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) @@ -549,7 +549,7 @@ public static ListOfDocumentEntity ParseSubmodelForV11( } public static ListOfDocumentEntity ParseSubmodelForV12( - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel subModel, AasxPredefinedConcepts.IdtaHandoverDocumentationV12 defs12, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) diff --git a/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs b/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs index 3eebfec83..c84a75825 100644 --- a/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs +++ b/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs @@ -35,7 +35,7 @@ public class ShelfAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private DocumentShelfOptions _options = null; private PluginEventStack _eventStack = null; @@ -144,7 +144,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, DocumentShelfOptions theOptions, PluginEventStack eventStack, @@ -187,7 +187,7 @@ public static ShelfAnyUiControl FillWithAnyUiControls( ShelfPreviewService previewService) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -1083,7 +1083,7 @@ private async Task DocumentEntity_MenuClick(DocumentEntity e, string menuItemHea _package.PrepareSupplementaryFileParameters(ref ptd, ref ptfn); // get content type - var mimeType = AdminShellPackageEnv.GuessMimeType(ptfn); + var mimeType = AdminShellPackageFileBasedEnv.GuessMimeType(ptfn); // call "add" var targetPath = _package.AddSupplementaryFileToStore( diff --git a/src/AasxPluginDocumentShelf/ShelfPreviewService.cs b/src/AasxPluginDocumentShelf/ShelfPreviewService.cs index 848168a93..5bf62ae6f 100644 --- a/src/AasxPluginDocumentShelf/ShelfPreviewService.cs +++ b/src/AasxPluginDocumentShelf/ShelfPreviewService.cs @@ -63,11 +63,11 @@ public class RenderEntity public AnyUiBitmapInfo Bitmap = null; - public AdminShellPackageEnv Package = null; + public AdminShellPackageFileBasedEnv Package = null; public DateTime LastUse = DateTime.Now; - public RenderEntity(AdminShellPackageEnv package, string supplFn) + public RenderEntity(AdminShellPackageFileBasedEnv package, string supplFn) { Package = package; PackageFn = package?.Filename; diff --git a/src/AasxPluginExportTable/Smt/ExportSmt.cs b/src/AasxPluginExportTable/Smt/ExportSmt.cs index 442962dc2..34c6b243e 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmt.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmt.cs @@ -43,7 +43,7 @@ namespace AasxPluginExportTable.Smt public class ExportSmt { protected LogInstance _log = null; - protected AdminShellPackageEnv _package = null; + protected AdminShellPackageEnvBase _package = null; protected Aas.ISubmodel _srcSm = null; protected ExportTableOptions _optionsAll = null; protected ExportSmtRecord _optionsSmt = null; @@ -333,7 +333,7 @@ protected void ProcessTables(Aas.IReferenceElement refel) public void ExportSmtToFile( LogInstance log, AnyUiContextPlusDialogs displayContext, - AdminShellPackageEnv package, + AdminShellPackageEnvBase package, Aas.ISubmodel submodel, ExportTableOptions optionsAll, ExportSmtRecord optionsSmt, diff --git a/src/AasxPluginGenericForms/GenericFormsAnyUiControl.cs b/src/AasxPluginGenericForms/GenericFormsAnyUiControl.cs index b127a4ca6..063ae8a0d 100644 --- a/src/AasxPluginGenericForms/GenericFormsAnyUiControl.cs +++ b/src/AasxPluginGenericForms/GenericFormsAnyUiControl.cs @@ -28,7 +28,7 @@ public class GenericFormsAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private AasxPluginGenericForms.GenericFormOptions _options = null; private PluginEventStack _eventStack = null; @@ -48,7 +48,7 @@ public class GenericFormsAnyUiControl public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, AasxPluginGenericForms.GenericFormOptions theOptions, PluginEventStack eventStack, @@ -76,7 +76,7 @@ public static GenericFormsAnyUiControl FillWithAnyUiControls( PluginOperationContextBase opContext) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) diff --git a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs index 4ea65ee21..1faae8d9f 100644 --- a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs +++ b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs @@ -37,7 +37,7 @@ public class ImageMapAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private ImageMapOptions _options = null; private PluginEventStack _eventStack = null; @@ -79,7 +79,7 @@ public ImageMapAnyUiControl() public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, ImageMapOptions theOptions, PluginEventStack eventStack, @@ -110,7 +110,7 @@ public static ImageMapAnyUiControl FillWithAnyUiControls( AasxPluginBase plugin) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -134,7 +134,7 @@ public static ImageMapAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // test trivial access @@ -154,7 +154,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, IEnumerable foundRecs, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // make an outer grid, very simple grid of two rows: header & body diff --git a/src/AasxPluginKnownSubmodels/KnownSubmodelAnyUiControl.cs b/src/AasxPluginKnownSubmodels/KnownSubmodelAnyUiControl.cs index a2a4610e2..040f60c96 100644 --- a/src/AasxPluginKnownSubmodels/KnownSubmodelAnyUiControl.cs +++ b/src/AasxPluginKnownSubmodels/KnownSubmodelAnyUiControl.cs @@ -27,7 +27,7 @@ public class KnownSubmodelAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private KnownSubmodelsOptions _options = null; private PluginEventStack _eventStack = null; @@ -58,7 +58,7 @@ public KnownSubmodelAnyUiControl() public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, KnownSubmodelsOptions theOptions, PluginEventStack eventStack, @@ -83,7 +83,7 @@ public static KnownSubmodelAnyUiControl FillWithAnyUiControls( object opanel) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -107,7 +107,7 @@ public static KnownSubmodelAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // test trivial access @@ -127,7 +127,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, IEnumerable foundRecs, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // make an outer grid, very simple grid of two rows: header & body @@ -197,7 +197,7 @@ protected void RenderPanelOutside( protected void RenderPanelInner( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, KnownSubmodelsOptionsRecord rec, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // access diff --git a/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs b/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs index dbc38cb48..9054da930 100644 --- a/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs +++ b/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs @@ -32,7 +32,7 @@ public partial class WpfMtpControlWrapper : UserControl // internal members private LogInstance theLog = null; - private AdminShellPackageEnv thePackage = null; + private AdminShellPackageFileBasedEnv thePackage = null; private Aas.Submodel theSubmodel = null; private AasxPluginMtpViewer.MtpViewerOptions theOptions = null; private PluginEventStack theEventStack = null; @@ -67,7 +67,7 @@ public WpfMtpControlWrapper() } public void Start( - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, AasxPluginMtpViewer.MtpViewerOptions theOptions, PluginEventStack eventStack, @@ -88,7 +88,7 @@ public static WpfMtpControlWrapper FillWithWpfControls( object masterDockPanel) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var master = masterDockPanel as DockPanel; if (package == null || sm == null || master == null) diff --git a/src/AasxPluginPlotting/PlotItem.cs b/src/AasxPluginPlotting/PlotItem.cs index bd79ff30a..4b3781728 100644 --- a/src/AasxPluginPlotting/PlotItem.cs +++ b/src/AasxPluginPlotting/PlotItem.cs @@ -274,7 +274,7 @@ public void UpdateValues(string lang = null) } public void RebuildFromSubmodel( - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, string lang) { // clear & access diff --git a/src/AasxPluginPlotting/PlottingViewControl.xaml.cs b/src/AasxPluginPlotting/PlottingViewControl.xaml.cs index 531707355..7798ee36f 100644 --- a/src/AasxPluginPlotting/PlottingViewControl.xaml.cs +++ b/src/AasxPluginPlotting/PlottingViewControl.xaml.cs @@ -39,7 +39,7 @@ public PlottingViewControl() InitializeComponent(); } - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private PlottingOptions _options = null; private PluginEventStack _pluginEvents = null; @@ -52,7 +52,7 @@ public PlottingViewControl() private string _defaultLang = null; public void Start( - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, PlottingOptions options, PluginEventStack pluginEvents, diff --git a/src/AasxPluginPlotting/Plugin.cs b/src/AasxPluginPlotting/Plugin.cs index bb1c5a962..61e23bfb8 100644 --- a/src/AasxPluginPlotting/Plugin.cs +++ b/src/AasxPluginPlotting/Plugin.cs @@ -167,7 +167,7 @@ public class AasxPlugin : AasxPluginBase if (action == "fill-panel-visual-extension" && args != null && args.Length >= 3) { // access - var package = args[0] as AdminShellPackageEnv; + var package = args[0] as AdminShellPackageFileBasedEnv; var sm = args[1] as Aas.Submodel; var master = args[2] as DockPanel; if (package == null || sm == null || master == null) diff --git a/src/AasxPluginProductChangeNotifications/PcnAnyUiControl.cs b/src/AasxPluginProductChangeNotifications/PcnAnyUiControl.cs index bd30608e6..86692139f 100644 --- a/src/AasxPluginProductChangeNotifications/PcnAnyUiControl.cs +++ b/src/AasxPluginProductChangeNotifications/PcnAnyUiControl.cs @@ -41,7 +41,7 @@ public class PcnAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private PcnOptions _options = null; private PluginEventStack _eventStack = null; @@ -87,7 +87,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, PcnOptions theOptions, PluginEventStack eventStack, @@ -122,7 +122,7 @@ public static PcnAnyUiControl FillWithAnyUiControls( AnyUiContextBase displayContext) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -146,7 +146,7 @@ public static PcnAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // test trivial access @@ -172,7 +172,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, IEnumerable foundRecs, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, PDPCN.CD_ProductChangeNotifications data) { @@ -1139,7 +1139,7 @@ protected void InnerDocAddTechnicalDataCompare( } protected void InnerDocIdentificationData( - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, AnyUiSmallWidgetToolkit uitk, AnyUiGrid grid, string header, @@ -1223,7 +1223,7 @@ protected void InnerDocAddProductClassifications( protected void RenderPanelInner( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, PcnOptionsRecord rec, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, PDPCN.CD_Record data) { @@ -1576,7 +1576,7 @@ public void Update(params object[] args) //================= protected async Task AddFromSmartPcnXml( - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm) { // access diff --git a/src/AasxPluginTechnicalData/TechnicalDataAnyUiControl.cs b/src/AasxPluginTechnicalData/TechnicalDataAnyUiControl.cs index 27e91c027..758e3cb49 100644 --- a/src/AasxPluginTechnicalData/TechnicalDataAnyUiControl.cs +++ b/src/AasxPluginTechnicalData/TechnicalDataAnyUiControl.cs @@ -36,7 +36,7 @@ public class TechnicalDataAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageEnv _package = null; + private AdminShellPackageFileBasedEnv _package = null; private Aas.Submodel _submodel = null; private TechnicalDataOptions _options = null; private PluginEventStack _eventStack = null; @@ -68,7 +68,7 @@ public TechnicalDataAnyUiControl() public void Start( LogInstance log, - AdminShellPackageEnv thePackage, + AdminShellPackageFileBasedEnv thePackage, Aas.Submodel theSubmodel, TechnicalDataOptions theOptions, PluginEventStack eventStack, @@ -96,7 +96,7 @@ public static TechnicalDataAnyUiControl FillWithAnyUiControls( AasxPluginBase plugin) { // access - var package = opackage as AdminShellPackageEnv; + var package = opackage as AdminShellPackageFileBasedEnv; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -120,7 +120,7 @@ public static TechnicalDataAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, string defaultLang = null) { // test trivial access @@ -146,7 +146,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, ConceptModelZveiTechnicalData theDefs, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, string defaultLang = null) { // make an outer grid, very simple grid of two rows: header & body @@ -282,7 +282,7 @@ protected class ClassificationRecord protected void RenderPanelHeader( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, ConceptModelZveiTechnicalData theDefs, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, string defaultLang = null) { // access @@ -563,7 +563,7 @@ protected void RenderTripleRowData( } protected void TableAddPropertyRows_Recurse( - ConceptModelZveiTechnicalData theDefs, string defaultLang, AdminShellPackageEnv package, + ConceptModelZveiTechnicalData theDefs, string defaultLang, AdminShellPackageFileBasedEnv package, List rows, List smec, int depth = 0) { // access @@ -714,7 +714,7 @@ protected void TableAddPropertyRows_Recurse( protected void RenderPanelInner( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, ConceptModelZveiTechnicalData theDefs, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, string defaultLang = null) { // access @@ -753,7 +753,7 @@ protected void RenderPanelInner( protected void RenderPanelFooter( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, ConceptModelZveiTechnicalData theDefs, - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, Aas.Submodel sm, string defaultLang = null) { // access diff --git a/src/AasxPredefinedConcepts/Convert/ConvertDocumentationHsuToSg2.cs b/src/AasxPredefinedConcepts/Convert/ConvertDocumentationHsuToSg2.cs index 4c193ed1b..a199b2c57 100644 --- a/src/AasxPredefinedConcepts/Convert/ConvertDocumentationHsuToSg2.cs +++ b/src/AasxPredefinedConcepts/Convert/ConvertDocumentationHsuToSg2.cs @@ -44,7 +44,7 @@ public override List CheckForOffers(Aas.IReferable currentRefe } public override bool ExecuteOffer( - AdminShellPackageEnv package, Aas.IReferable currentReferable, ConvertOfferBase offerBase, + AdminShellPackageEnvBase package, Aas.IReferable currentReferable, ConvertOfferBase offerBase, bool deleteOldCDs, bool addNewCDs) { // access diff --git a/src/AasxPredefinedConcepts/Convert/ConvertDocumentationSg2ToHsu.cs b/src/AasxPredefinedConcepts/Convert/ConvertDocumentationSg2ToHsu.cs index 4af25bac0..4345fe0a3 100644 --- a/src/AasxPredefinedConcepts/Convert/ConvertDocumentationSg2ToHsu.cs +++ b/src/AasxPredefinedConcepts/Convert/ConvertDocumentationSg2ToHsu.cs @@ -48,7 +48,7 @@ public override List CheckForOffers(Aas.IReferable currentRefe return res; } - public override bool ExecuteOffer(AdminShellPackageEnv package, Aas.IReferable currentReferable, + public override bool ExecuteOffer(AdminShellPackageEnvBase package, Aas.IReferable currentReferable, ConvertOfferBase offerBase, bool deleteOldCDs, bool addNewCDs) { // access diff --git a/src/AasxPredefinedConcepts/Convert/ConvertDocumentationSg2ToV11.cs b/src/AasxPredefinedConcepts/Convert/ConvertDocumentationSg2ToV11.cs index 5bd729c70..0b282e319 100644 --- a/src/AasxPredefinedConcepts/Convert/ConvertDocumentationSg2ToV11.cs +++ b/src/AasxPredefinedConcepts/Convert/ConvertDocumentationSg2ToV11.cs @@ -42,7 +42,7 @@ public override List CheckForOffers(Aas.IReferable currentRefe return res; } - public override bool ExecuteOffer(AdminShellPackageEnv package, Aas.IReferable currentReferable, + public override bool ExecuteOffer(AdminShellPackageEnvBase package, Aas.IReferable currentReferable, ConvertOfferBase offerBase, bool deleteOldCDs, bool addNewCDs) { // access diff --git a/src/AasxPredefinedConcepts/Convert/ConvertNameplateHsuToZveiV10.cs b/src/AasxPredefinedConcepts/Convert/ConvertNameplateHsuToZveiV10.cs index 1a53aecbf..3a5905f1e 100644 --- a/src/AasxPredefinedConcepts/Convert/ConvertNameplateHsuToZveiV10.cs +++ b/src/AasxPredefinedConcepts/Convert/ConvertNameplateHsuToZveiV10.cs @@ -43,7 +43,7 @@ public override List CheckForOffers(Aas.IReferable currentRefe return res; } - public override bool ExecuteOffer(AdminShellPackageEnv package, Aas.IReferable currentReferable, + public override bool ExecuteOffer(AdminShellPackageEnvBase package, Aas.IReferable currentReferable, ConvertOfferBase offerBase, bool deleteOldCDs, bool addNewCDs) { // access diff --git a/src/AasxPredefinedConcepts/Convert/ConvertPredefinedConcepts.cs b/src/AasxPredefinedConcepts/Convert/ConvertPredefinedConcepts.cs index 10ee46f37..fdd47f9eb 100644 --- a/src/AasxPredefinedConcepts/Convert/ConvertPredefinedConcepts.cs +++ b/src/AasxPredefinedConcepts/Convert/ConvertPredefinedConcepts.cs @@ -37,7 +37,7 @@ public virtual List CheckForOffers(Aas.IReferable currentRefer } public virtual bool ExecuteOffer( - AdminShellPackageEnv package, Aas.IReferable currentReferable, ConvertOfferBase offer, + AdminShellPackageEnvBase package, Aas.IReferable currentReferable, ConvertOfferBase offer, bool deleteOldCDs, bool addNewCDs) { return true; diff --git a/src/AasxPredefinedConcepts/Convert/ConvertTechnicalDataToFlat.cs b/src/AasxPredefinedConcepts/Convert/ConvertTechnicalDataToFlat.cs index 8a81c9393..f2f6a425c 100644 --- a/src/AasxPredefinedConcepts/Convert/ConvertTechnicalDataToFlat.cs +++ b/src/AasxPredefinedConcepts/Convert/ConvertTechnicalDataToFlat.cs @@ -43,7 +43,7 @@ public override List CheckForOffers(Aas.IReferable currentRefe } public override bool ExecuteOffer( - AdminShellPackageEnv package, Aas.IReferable currentReferable, + AdminShellPackageEnvBase package, Aas.IReferable currentReferable, ConvertOfferBase offerBase, bool deleteOldCDs, bool addNewCDs) { // access diff --git a/src/AasxPredefinedConcepts/Convert/ConvertTechnicalDataV10ToV11.cs b/src/AasxPredefinedConcepts/Convert/ConvertTechnicalDataV10ToV11.cs index 0fbab92b6..da8a4175f 100644 --- a/src/AasxPredefinedConcepts/Convert/ConvertTechnicalDataV10ToV11.cs +++ b/src/AasxPredefinedConcepts/Convert/ConvertTechnicalDataV10ToV11.cs @@ -111,7 +111,7 @@ private void RecurseToCopyTechnicalProperties( } } - public override bool ExecuteOffer(AdminShellPackageEnv package, Aas.IReferable currentReferable, + public override bool ExecuteOffer(AdminShellPackageEnvBase package, Aas.IReferable currentReferable, ConvertOfferBase offerBase, bool deleteOldCDs, bool addNewCDs) { // access diff --git a/src/AasxToolkit/Execution.cs b/src/AasxToolkit/Execution.cs index 866404472..23d17fe0e 100644 --- a/src/AasxToolkit/Execution.cs +++ b/src/AasxToolkit/Execution.cs @@ -12,7 +12,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasFormUtils = AasxIntegrationBase.AasForms.AasFormUtils; using AasSchemaValidation = AdminShellNS.AasSchemaValidation; using AasValidationRecordList = AdminShellNS.AasValidationRecordList; -using AdminShellPackageEnv = AdminShellNS.AdminShellPackageEnv; +using AdminShellPackageFileBasedEnv = AdminShellNS.AdminShellPackageFileBasedEnv; using AdminShellUtil = AdminShellNS.AdminShellUtil; using AmlExport = AasxAmlImExport.AmlExport; using AmlImport = AasxAmlImExport.AmlImport; @@ -35,7 +35,7 @@ public static class Execution public static int Execute(IReadOnlyList instructions) { // # Context - AdminShellPackageEnv package = null; + AdminShellPackageFileBasedEnv package = null; // # Execution loop @@ -73,12 +73,12 @@ public static int Execute(IReadOnlyList instructions) { if (load.Path.EndsWith(".aml")) { - package = new AdminShellPackageEnv(); + package = new AdminShellPackageFileBasedEnv(); AmlImport.ImportInto(package, load.Path); } else { - package = new AdminShellPackageEnv(load.Path); + package = new AdminShellPackageFileBasedEnv(load.Path); } } catch (Exception ex) diff --git a/src/AasxToolkit/Extract.cs b/src/AasxToolkit/Extract.cs index 289ca4034..4a4a7e809 100644 --- a/src/AasxToolkit/Extract.cs +++ b/src/AasxToolkit/Extract.cs @@ -17,7 +17,7 @@ namespace AasxToolkit public static class Extract { public static void Extract2770Doc( - AdminShellPackageEnv package, + AdminShellPackageFileBasedEnv package, string findSys, string findClass, string targetFn) { // access diff --git a/src/AasxToolkit/Generate.cs b/src/AasxToolkit/Generate.cs index bf0417ded..f2f5318de 100644 --- a/src/AasxToolkit/Generate.cs +++ b/src/AasxToolkit/Generate.cs @@ -20,7 +20,7 @@ namespace AasxToolkit { public static class Generate { - public static AdminShellPackageEnv GeneratePackage(string preffn = "*") + public static AdminShellPackageFileBasedEnv GeneratePackage(string preffn = "*") { // MAKE or LOAD prefs @@ -193,12 +193,12 @@ public static AdminShellPackageEnv GeneratePackage(string preffn = "*") // Make PACKAGE // - AdminShellPackageEnv package = null; + AdminShellPackageFileBasedEnv package = null; try { Log.WriteLine(2, "Creating package in RAM .."); - package = new AdminShellPackageEnv(aasenv1); + package = new AdminShellPackageFileBasedEnv(aasenv1); // supplementary files Log.WriteLine(2, "Adding supplementary files .."); @@ -284,7 +284,7 @@ public static Submodel CreateSubmodelCad( // FILE var propFile = new AasCore.Aas3_0.File("", idShort: "File", category: "PARAMETER", semanticId: cdFile.GetCdReference()); propGroup.Add(propFile); - propFile.ContentType = AdminShellPackageEnv.GuessMimeType(fr.fn); + propFile.ContentType = AdminShellPackageFileBasedEnv.GuessMimeType(fr.fn); propFile.Value = "" + fr.targetdir.Trim() + Path.GetFileName(fr.fn); // FILEFORMAT @@ -486,7 +486,7 @@ public static Submodel CreateSubmodelDocumentationBasedOnVDI2770( var file = new AasCore.Aas3_0.File("", idShort: cd.GetDefaultPreferredName(), category: "CONSTANT", semanticId: cd.GetReference()); { p1.Add(file); - file.ContentType = AdminShellPackageEnv.GuessMimeType(fn); + file.ContentType = AdminShellPackageFileBasedEnv.GuessMimeType(fn); file.Value = "" + targetdir.Trim() + Path.GetFileName(fn); } } @@ -497,7 +497,7 @@ public static Submodel CreateSubmodelDocumentationBasedOnVDI2770( var file = new AasCore.Aas3_0.File("", idShort: cd.GetDefaultPreferredName(), category: "CONSTANT", semanticId: cd.GetReference()); { p1.Add(file); - file.ContentType = AdminShellPackageEnv.GuessMimeType(url); + file.ContentType = AdminShellPackageFileBasedEnv.GuessMimeType(url); file.Value = "" + url.Trim(); } diff --git a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs index c682dbd99..9757bed1c 100644 --- a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs +++ b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs @@ -629,7 +629,7 @@ public void RebuildAasxElements( // more? if (packages.Repositories != null && selector == PackageCentral.Selector.MainAuxFileRepo) { - var pkg = new AdminShellPackageEnv(); + var pkg = new AdminShellPackageFileBasedEnv(); foreach (var fr in packages.Repositories) fr.PopulateFakePackage(pkg); diff --git a/src/BlazorExplorer/BlazorVisualElements.cs b/src/BlazorExplorer/BlazorVisualElements.cs index 813b3ced4..ec0735464 100644 --- a/src/BlazorExplorer/BlazorVisualElements.cs +++ b/src/BlazorExplorer/BlazorVisualElements.cs @@ -206,7 +206,7 @@ public void RebuildAasxElements( // more? if (packages.Repositories != null && selector == PackageCentral.Selector.MainAuxFileRepo) { - var pkg = new AdminShellPackageEnv(); + var pkg = new AdminShellPackageFileBasedEnv(); foreach (var fr in packages.Repositories) fr.PopulateFakePackage(pkg); diff --git a/src/BlazorExplorer/Data/AASService.cs b/src/BlazorExplorer/Data/AASService.cs index 26ff4896c..4f0a39693 100644 --- a/src/BlazorExplorer/Data/AASService.cs +++ b/src/BlazorExplorer/Data/AASService.cs @@ -95,7 +95,7 @@ public Item FindReferable(Aas.IReferable rf, string pluginTag) foreach (var ch in res.Childs) if (ch != null && ch.Type == "Plugin" - && ch.Tag is Tuple tag && tag?.Item4?.Tag?.Trim().ToLower() == pluginTag.Trim().ToLower()) return ch; @@ -116,7 +116,7 @@ public Item FindSubmodelPlugin(string smid, string pluginTag) return false; return parit.Referable is Aas.Submodel sm && sm?.Id == smid && it.Type == "Plugin" - && it.Tag is Tuple tag && tag?.Item4?.Tag?.Trim().ToLower() == pluginTag.Trim().ToLower(); }).FirstOrDefault(); @@ -247,7 +247,7 @@ public void buildTree(BlazorSession bi) Referable = sm, envIndex = i, Text = "PLUGIN", - Tag = new Tuple (bi.env, sm, lpi, ext), Type = "Plugin" diff --git a/src/BlazorExplorer/Data/AasxInfoBox.cs b/src/BlazorExplorer/Data/AasxInfoBox.cs index bae6f77b8..a072a1149 100644 --- a/src/BlazorExplorer/Data/AasxInfoBox.cs +++ b/src/BlazorExplorer/Data/AasxInfoBox.cs @@ -50,7 +50,7 @@ public class AasxInfoBox /// public string HtmlImageData { get; set; } - public void SetInfos(Aas.IAssetAdministrationShell aas, AdminShellPackageEnv env) + public void SetInfos(Aas.IAssetAdministrationShell aas, AdminShellPackageEnvBase env) { // access AasId = ""; diff --git a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs index 27278f135..6b1d4e3f7 100644 --- a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs +++ b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs @@ -125,7 +125,7 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() public void UiLoadPackageWithNew( PackageCentralItem packItem, - AdminShellPackageEnv takeOverEnv = null, + AdminShellPackageFileBasedEnv takeOverEnv = null, string loadLocalFilename = null, string info = null, bool onlyAuxiliary = false, diff --git a/src/BlazorExplorer/Data/BlazorSession.cs b/src/BlazorExplorer/Data/BlazorSession.cs index 16402fb56..e6ce41c15 100644 --- a/src/BlazorExplorer/Data/BlazorSession.cs +++ b/src/BlazorExplorer/Data/BlazorSession.cs @@ -168,7 +168,7 @@ public PackageCentral PackageCentral // dead-csharp on // old stuff, to be refactored - public AdminShellPackageEnv env = null; + public AdminShellPackageFileBasedEnv env = null; public IndexOfSignificantAasElements significantElements = null; public string[] aasxFiles = new string[1]; From 6c9c7af54987e5b4a5cebd75f92b41e1b16fdb86 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Fri, 20 Sep 2024 12:21:26 +0200 Subject: [PATCH 07/99] * package container * GET und PUT * tainting of Identifiables * Visual feedback * first UI --- src/AasCore.Aas3_0/DiaryData/ITaintedData.cs | 13 + .../DiaryData/TaintedDataDef.cs | 45 ++ src/AasCore.Aas3_0/types.cs | 38 +- .../AasxCsharpLibrary.csproj | 1 + .../AdminShellBasicExtensions.cs | 8 + .../AdminShellPackageFileBasedEnv.cs | 47 +- src/AasxCsharpLibrary/AdminShellUtil.cs | 17 + .../ExtendAssetAdministrationShell.cs | 16 +- .../Extensions/ExtendCollection.cs | 10 + .../Extensions/ExtendConceptDescription.cs | 4 +- .../Extensions/ExtendEnvironment.cs | 33 +- .../AnyUI/AnyUiMagickHelper.cs | 2 +- src/AasxOpcUa2Client/AasOpcUaClient2.cs | 10 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 88 ++- .../options-debug.MIHO.json | 4 +- src/AasxPackageLogic/DispEditHelperBasics.cs | 27 +- .../DispEditHelperCopyPaste.cs | 4 +- .../DispEditHelperEntities.cs | 130 +++- .../DispEditHelperMultiElement.cs | 2 +- src/AasxPackageLogic/EclassUtils.cs | 1 + src/AasxPackageLogic/EmptyListVisitor.cs | 1 - src/AasxPackageLogic/ExplorerMenuFactory.cs | 1 + .../MainWindowAnyUiDialogs.cs | 58 ++ .../PackageCentral/AasOnDemandEnvironment.cs | 252 +++++++ .../AdminShellPackageDynamicFetchEnv.cs | 313 ++++++++ .../PackageCentral/OnDemandList.cs | 152 ++++ .../PackageCentral/PackageContainerBase.cs | 6 + .../PackageCentral/PackageContainerFactory.cs | 1 + .../PackageContainerHttpRepoSubset.cs | 676 +++++++++++++++++- .../PackageContainerLocalFile.cs | 7 +- .../PackageContainerNetworkHttpFile.cs | 3 +- .../PackageContainerUserFile.cs | 7 +- .../PackageCentral/PackageHttpDownloadUtil.cs | 158 ++-- .../PackageContainerFakeAnswers.json | 7 +- src/AasxPackageLogic/VisualAasxElements.cs | 171 ++++- .../NameplateAnyUiControl.cs | 6 +- .../NameplateData.cs | 6 +- src/AasxPluginDigitalNameplate/Plugin.cs | 2 +- .../DiplayVisualAasxElements.xaml.cs | 7 + src/BlazorExplorer/Data/BlazorSession.cs | 2 +- 40 files changed, 2149 insertions(+), 187 deletions(-) create mode 100644 src/AasCore.Aas3_0/DiaryData/ITaintedData.cs create mode 100644 src/AasCore.Aas3_0/DiaryData/TaintedDataDef.cs create mode 100644 src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs create mode 100644 src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs create mode 100644 src/AasxPackageLogic/PackageCentral/OnDemandList.cs diff --git a/src/AasCore.Aas3_0/DiaryData/ITaintedData.cs b/src/AasCore.Aas3_0/DiaryData/ITaintedData.cs new file mode 100644 index 000000000..94933fb4c --- /dev/null +++ b/src/AasCore.Aas3_0/DiaryData/ITaintedData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AdminShellNS.DiaryData +{ + public interface ITaintedData + { + TaintedDataDef TaintedData { get; } + } +} diff --git a/src/AasCore.Aas3_0/DiaryData/TaintedDataDef.cs b/src/AasCore.Aas3_0/DiaryData/TaintedDataDef.cs new file mode 100644 index 000000000..f921530d4 --- /dev/null +++ b/src/AasCore.Aas3_0/DiaryData/TaintedDataDef.cs @@ -0,0 +1,45 @@ +using AasCore.Aas3_0; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using System.Xml.Serialization; + +namespace AdminShellNS.DiaryData +{ + public class TaintedDataDef + { + [XmlIgnore] + [JsonIgnore] + private DateTime? _tainted = null; + + [XmlIgnore] + [JsonIgnore] + public DateTime? Tainted + { + get { return _tainted; } + set { _tainted = value; } + } + + public static void TaintIdentifiable(IReferable element) + { + // trivial + if (element == null) + return; + + // find identifiable + var el = element; + while (el != null) + { + // found? + if (el is IIdentifiable && el is ITaintedData itd) + { + itd.TaintedData.Tainted = DateTime.UtcNow; + return; + } + + // up + el = (el as IReferable)?.Parent as IReferable; + } + } + } +} diff --git a/src/AasCore.Aas3_0/types.cs b/src/AasCore.Aas3_0/types.cs index f72cd5894..e9d29c711 100644 --- a/src/AasCore.Aas3_0/types.cs +++ b/src/AasCore.Aas3_0/types.cs @@ -469,7 +469,7 @@ public interface IReferable : IHasExtensions, IDiaryData /// /// An element that has a globally unique identifier. /// - public interface IIdentifiable : IReferable + public interface IIdentifiable : IReferable, ITaintedData { /// /// Administrative information of an identifiable element. @@ -1317,6 +1317,12 @@ public class AssetAdministrationShell : IAssetAdministrationShell [JsonIgnore] public DiaryDataDef DiaryData { get { return _diaryData; } } + [JsonIgnore] + private TaintedDataDef _taintedData = new TaintedDataDef(); + + [JsonIgnore] + public TaintedDataDef TaintedData { get { return _taintedData; } } + #endregion /// @@ -2460,6 +2466,12 @@ public class Submodel : ISubmodel [JsonIgnore] public DiaryDataDef DiaryData { get { return _diaryData; } } + [JsonIgnore] + private TaintedDataDef _taintedData = new TaintedDataDef(); + + [JsonIgnore] + public TaintedDataDef TaintedData { get { return _taintedData; } } + #endregion /// @@ -10741,6 +10753,12 @@ public class ConceptDescription : IConceptDescription [JsonIgnore] public DiaryDataDef DiaryData { get { return _diaryData; } } + [JsonIgnore] + private TaintedDataDef _taintedData = new TaintedDataDef(); + + [JsonIgnore] + public TaintedDataDef TaintedData { get { return _taintedData; } } + #endregion /// @@ -11910,17 +11928,17 @@ public interface IEnvironment : IClass, IDiaryData /// /// Asset administration shell /// - public List? AssetAdministrationShells { get; set; } + public IList? AssetAdministrationShells { get; set; } /// /// Submodel /// - public List? Submodels { get; set; } + public IList? Submodels { get; set; } /// /// Concept description /// - public List? ConceptDescriptions { get; set; } + public IList? ConceptDescriptions { get; set; } /// /// Iterate over AssetAdministrationShells, if set, and otherwise return an empty enumerable. @@ -11951,17 +11969,17 @@ public class Environment : IEnvironment /// /// Asset administration shell /// - public List? AssetAdministrationShells { get; set; } + public IList? AssetAdministrationShells { get; set; } /// /// Submodel /// - public List? Submodels { get; set; } + public IList? Submodels { get; set; } /// /// Concept description /// - public List? ConceptDescriptions { get; set; } + public IList? ConceptDescriptions { get; set; } /// /// Iterate over AssetAdministrationShells, if set, and otherwise return an empty enumerable. @@ -12116,9 +12134,9 @@ public T Transform( } public Environment( - List? assetAdministrationShells = null, - List? submodels = null, - List? conceptDescriptions = null) + IList? assetAdministrationShells = null, + IList? submodels = null, + IList? conceptDescriptions = null) { AssetAdministrationShells = assetAdministrationShells; Submodels = submodels; diff --git a/src/AasxCsharpLibrary/AasxCsharpLibrary.csproj b/src/AasxCsharpLibrary/AasxCsharpLibrary.csproj index ef47300be..ea20fca2d 100644 --- a/src/AasxCsharpLibrary/AasxCsharpLibrary.csproj +++ b/src/AasxCsharpLibrary/AasxCsharpLibrary.csproj @@ -33,6 +33,7 @@ + diff --git a/src/AasxCsharpLibrary/AdminShellBasicExtensions.cs b/src/AasxCsharpLibrary/AdminShellBasicExtensions.cs index b52c70969..0ca1e993c 100644 --- a/src/AasxCsharpLibrary/AdminShellBasicExtensions.cs +++ b/src/AasxCsharpLibrary/AdminShellBasicExtensions.cs @@ -44,6 +44,14 @@ public static IEnumerable ForEachSafe(this List list) yield return x; } + public static void ForEach(this IList list, Action action) + { + if (list == null) + return; + foreach (var x in list) + action?.Invoke(x); + } + /// /// Multiple a string into a iterable list /// diff --git a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs index 3c8fce318..feb8981bd 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs @@ -345,6 +345,14 @@ public virtual Stream GetStreamFromUriOrLocalPackage(string uriString, return null; } + /// + /// This is intended to be the "new" one + /// + public virtual Stream GetThumbnailStreamFromAasOrPackage(string aasId) + { + return null; + } + public virtual ListOfAasSupplementaryFile GetListOfSupplementaryFiles() { return null; @@ -1633,7 +1641,7 @@ public long GetStreamSizeFromPackage(string uriString) /// Ensures: ///
  • result == null || result.CanRead
/// - public virtual Stream GetLocalThumbnailStream(ref Uri thumbUri) + public override Stream GetLocalThumbnailStream(ref Uri thumbUri) { // access if (_openPackage == null) @@ -1664,7 +1672,42 @@ public virtual Stream GetLocalThumbnailStream(ref Uri thumbUri) } return result; - } + } + + public override Stream GetThumbnailStreamFromAasOrPackage(string aasId) + { + // find aas? + var aas = AasEnv?.FindAasById(aasId); + if (aas?.AssetInformation?.DefaultThumbnail?.Path?.HasContent() == true) + { + try + { + // Note: could also use http://... + var s1 = GetLocalStreamFromPackage(uriString: aas.AssetInformation.DefaultThumbnail.Path); + if (s1 != null) + return s1; + } catch (Exception ex) + { + LogInternally.That.CompletelyIgnoredError(ex); + } + } + + // or local package? + try + { + Uri dummy = null; + var s2 = GetLocalThumbnailStream(ref dummy); + if (s2 != null) + return s2; + } + catch (Exception ex) + { + LogInternally.That.CompletelyIgnoredError(ex); + } + + // no + return null; + } public override ListOfAasSupplementaryFile GetListOfSupplementaryFiles() { diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index 906e60307..ef5f59cd8 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -9,6 +9,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxCompatibilityModels; using Extensions; +using Microsoft.IdentityModel.Tokens; using System; using System.Collections; using System.Collections.Generic; @@ -1193,6 +1194,22 @@ public static string Base64Decode(string base64EncodedData) return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); } + // + // Base 64 URL + // + + // Note: requires Microsoft.IdentityModel.Tokens + + public static string Base64UrlEncode(string plainUrl) + { + return Base64UrlEncoder.Encode(plainUrl); + } + + public static string Base64UrlDecode(string base64Url) + { + return Base64UrlEncoder.Decode(base64Url); + } + /// /// Tries to add scheme etc. to form a valid URI. /// uri with starting '/' are left untouched. diff --git a/src/AasxCsharpLibrary/Extensions/ExtendAssetAdministrationShell.cs b/src/AasxCsharpLibrary/Extensions/ExtendAssetAdministrationShell.cs index f929c7d28..e6612902e 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendAssetAdministrationShell.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendAssetAdministrationShell.cs @@ -32,17 +32,19 @@ public static Tuple ToCaptionInfo(this IAssetAdministrationShell public static IEnumerable FindAllReferences(this IAssetAdministrationShell assetAdministrationShell) { - // dead-csharp off - // Asset - //TODO (jtikekar, 0000-00-00): support asset - //if (assetAdministrationShell.AssetInformation != null) - // yield return new LocatedReference(assetAdministrationShell, assetAdministrationShell.AssetInformation); - // dead-csharp on - // Submodel references foreach (var r in assetAdministrationShell.AllSubmodels()) yield return new LocatedReference(assetAdministrationShell, r); } + public static IEnumerable FindAllSubmodelReferences(this IAssetAdministrationShell assetAdministrationShell) + { + // unique set of references + var refs = new List(); + foreach (var smr in assetAdministrationShell.AllSubmodels()) + refs.AddIfNew(new LocatedReference() { Identifiable = assetAdministrationShell, Reference = smr }); + return refs; + } + #endregion public static IReference FindSubmodelReference(this IAssetAdministrationShell aas, IReference smRef) diff --git a/src/AasxCsharpLibrary/Extensions/ExtendCollection.cs b/src/AasxCsharpLibrary/Extensions/ExtendCollection.cs index 3acde94a4..e1dde1f0f 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendCollection.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendCollection.cs @@ -13,5 +13,15 @@ public static bool IsNullOrEmpty(this List list) return true; } + + public static bool IsNullOrEmpty(this IList list) + { + if (list != null && list.Count != 0) + { + return false; + } + + return true; + } } } diff --git a/src/AasxCsharpLibrary/Extensions/ExtendConceptDescription.cs b/src/AasxCsharpLibrary/Extensions/ExtendConceptDescription.cs index 40bb2bdc3..2ae87b156 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendConceptDescription.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendConceptDescription.cs @@ -298,7 +298,7 @@ public static void AddIsCaseOf(this IConceptDescription cd, /// /// Returns false, if there is another element with same idShort in the list /// - public static bool CheckIdShortIsUnique(this List cds, string idShort) + public static bool CheckIdShortIsUnique(this IList cds, string idShort) { idShort = idShort?.Trim(); if (idShort == null || idShort.Length < 1) @@ -318,7 +318,7 @@ public static bool CheckIdShortIsUnique(this List cds, stri /// /// Creates ids with numerical index according to template string, until a unique idShort is found /// - public static string IterateIdShortTemplateToBeUnique(this List cds, string idShortTemplate, int maxNum) + public static string IterateIdShortTemplateToBeUnique(this IList cds, string idShortTemplate, int maxNum) { if (idShortTemplate == null || maxNum < 1 || !idShortTemplate.Contains("{0")) return null; diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index 8701094c0..e4586ef1d 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -468,7 +468,7 @@ public static IConceptDescription Add(this AasCore.Aas3_0.IEnvironment env, ICon if (cd == null) return null; if (env.ConceptDescriptions == null) - env.ConceptDescriptions = new(); + env.ConceptDescriptions = new List(); env.ConceptDescriptions.Add(cd); return cd; } @@ -505,7 +505,7 @@ public static ISubmodel Add(this AasCore.Aas3_0.IEnvironment env, ISubmodel sm) if (sm == null) return null; if (env.Submodels == null) - env.Submodels = new(); + env.Submodels = new List(); env.Submodels.Add(sm); return sm; } @@ -519,7 +519,7 @@ public static IAssetAdministrationShell Add(this AasCore.Aas3_0.IEnvironment env if (aas == null) return null; if (env.AssetAdministrationShells == null) - env.AssetAdministrationShells = new(); + env.AssetAdministrationShells = new List(); env.AssetAdministrationShells.Add(aas); return aas; } @@ -972,7 +972,7 @@ public static IEnumerable FindAllSubmodelReferences( // unique set of references var refs = new List(); foreach (var aas in environment.AllAssetAdministrationShells()) - foreach (var smr in aas?.Submodels) + foreach (var smr in aas?.AllSubmodels()) refs.AddIfNew(new LocatedReference() { Identifiable = aas, Reference = smr}); // only existing @@ -982,6 +982,31 @@ public static IEnumerable FindAllSubmodelReferences( yield return lr; } + /// + /// Warning: very inefficient! + /// + public static IEnumerable FindAllSemanticIdsForAas( + this AasCore.Aas3_0.IEnvironment environment, + IAssetAdministrationShell aas) + { + // unique set of references + var refs = new List(); + foreach (var smr in aas?.AllSubmodels()) + { + var sm = environment.FindSubmodel(smr); + sm?.RecurseOnSubmodelElements(null, (state, parents, sme) => + { + if (sme.SemanticId != null) + refs.AddIfNew(new LocatedReference() { Identifiable = sm, Reference = sme.SemanticId }); + + // recurse + return true; + }); + } + + return refs; + } + /// /// Tries renaming an Identifiable, specifically: the identification of an Identifiable and /// all references to it. diff --git a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs index ab6963e93..93307ae9a 100644 --- a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs +++ b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs @@ -83,7 +83,7 @@ public static AnyUiBitmapInfo CreateAnyUiBitmapFromResource(string path, return null; } - public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageFileBasedEnv package, string path) + public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageEnvBase package, string path) { if (package == null || path == null) return null; diff --git a/src/AasxOpcUa2Client/AasOpcUaClient2.cs b/src/AasxOpcUa2Client/AasOpcUaClient2.cs index 4da04aeeb..00d85657c 100644 --- a/src/AasxOpcUa2Client/AasOpcUaClient2.cs +++ b/src/AasxOpcUa2Client/AasOpcUaClient2.cs @@ -107,7 +107,15 @@ public async Task StartClientAsync() SecurityPolicyUris.None); // no encryption // try opening a session and reading a few nodes. - await _channel.OpenAsync(); + try + { + await _channel.OpenAsync(); + } catch (Exception ex) + { + ClientStatus = AasOpcUaClientStatus.ErrorCreateSession; + _log?.Error(ex, "open async"); + return; + } // ok ClientStatus = AasOpcUaClientStatus.Running; diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 54b63b13b..4109917bb 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -705,7 +705,6 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie var asset = tvlaas.theAas.AssetInformation; if (asset != null) { - // text id if (asset.GlobalAssetId != null) this.AssetId.Text = WpfStringAddWrapChars( @@ -717,8 +716,37 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie // identify which stream to use.. var picFound = false; - // specific for the AAS - if (PackageCentral.MainAvailable) + // new approach + if (PackageCentral.MainAvailable) + try + { + var thumbStream = PackageCentral.Main.GetThumbnailStreamFromAasOrPackage(tvlaas.theAas.Id); + if (thumbStream != null) + { + // load image + var bi = new BitmapImage(); + bi.BeginInit(); + + // See https://stackoverflow.com/a/5346766/1600678 + bi.CacheOption = BitmapCacheOption.OnLoad; + + bi.StreamSource = thumbStream; + bi.EndInit(); + + this.AssetPic.Source = bi; + picFound = true; + thumbStream.Close(); + } + } + catch (Exception ex) + { + AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); + } + + + // specific for the AAS +#if __old1 + if (PackageCentral.MainAvailable) try { if (asset?.DefaultThumbnail?.Path?.HasContent() == true) @@ -747,10 +775,10 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie { AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); } +#endif - - // no, ask online server? - if (!picFound && this.theOnlineConnection != null + // no, ask online server? + if (!picFound && this.theOnlineConnection != null && this.theOnlineConnection.IsValid() && this.theOnlineConnection.IsConnected()) try @@ -776,8 +804,9 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); } - // no, from the AASX? - if (!picFound && PackageCentral.MainAvailable) +#if __old2 + // no, from the AASX? + if (!picFound && PackageCentral.MainAvailable) try { using (var thumbStream = PackageCentral.Main.GetLocalThumbnailStream()) @@ -800,10 +829,8 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie catch (Exception ex) { AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); - } - - - + } +#endif } catch (Exception ex) { @@ -826,7 +853,7 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie } - #endregion +#endregion #region Callbacks //=============== @@ -2334,6 +2361,27 @@ private void MainTimer_CheckDiaryDateToEmitEvents( } } + private void MainTimer_CheckTaintedIdentifiables( + DateTime lastTime, + Aas.IEnvironment env) + { + // trivial + if (env == null || DisplayElements == null) + return; + + // find visual elements for Identifiables and check for tainted state + foreach (var ve in DisplayElements.FindAllVisualElementTopToIdentifiable()) + if (ve is ITaintableIdentifiable ttidf) + { + var tt = ttidf.GetTaintedTime(); + if (tt == null || tt < lastTime) + continue; + + // kick off redisplay + ve.RefreshFromMainData(); + } + } + protected EventHandlingStatus _eventHandling = new EventHandlingStatus(); private void MainTimer_PeriodicalTaskForSelectedEntity() @@ -2554,6 +2602,7 @@ public void MainTaimer_HandleIncomingAasEvents() } private DateTime _mainTimer_LastCheckForDiaryEvents; + private DateTime _mainTimer_LastCheckForTaintedIdentifiables; private DateTime _mainTimer_LastCheckForAnimationElements = DateTime.Now; private bool _mainTimer_PendingReIndexElements = false; @@ -2561,10 +2610,12 @@ public void MainTaimer_HandleIncomingAasEvents() private async Task MainTimer_Tick(object sender, EventArgs e) { + // different functions MainTimer_HandleLogMessages(); await MainTimer_HandleEntityPanel(); await MainTimer_HandleApplicationEvents(); + // diary dates -> events, animation if (PackageCentral?.MainItem?.Container?.SignificantElements != null) { MainTimer_CheckDiaryDateToEmitEvents( @@ -2587,8 +2638,14 @@ private async Task MainTimer_Tick(object sender, EventArgs e) } } - // do re-index? - var deltaSecs2 = (DateTime.Now - _mainTimer_LastCheckForReIndexElements).TotalSeconds; + // flags for tainted Identifiables + MainTimer_CheckTaintedIdentifiables( + _mainTimer_LastCheckForTaintedIdentifiables, + PackageCentral.MainItem.Container.Env?.AasEnv); + _mainTimer_LastCheckForTaintedIdentifiables = DateTime.UtcNow; + + // pending re-index? + var deltaSecs2 = (DateTime.Now - _mainTimer_LastCheckForReIndexElements).TotalSeconds; if (deltaSecs2 >= 1.0 && _mainTimer_PendingReIndexElements) { // dis-engage @@ -2601,6 +2658,7 @@ private async Task MainTimer_Tick(object sender, EventArgs e) Log.Singleton.Info("Re-indexing Identifiables for faster access."); } + // normal stuff MainTimer_PeriodicalTaskForSelectedEntity(); MainTaimer_HandleIncomingAasEvents(); DisplayElements.UpdateFromQueuedEvents(); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index d4ecb6be4..e9fc3f993 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -40,7 +40,9 @@ // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Draft_v22.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Examples_v08.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_v20_spiel.aasx", - "AasxToLoad": "https://cloudrepo.aas-voyager.com/shells/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzY1NDNfNjAzMl8zMDEyXzU5NzM=", + // "AasxToLoad": "https://cloudrepo.aas-voyager.com/shells/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzY1NDNfNjAzMl8zMDEyXzU5NzM=", + // "AasxToLoad": "https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NGMjA0MTAxUjE0MDAvYWFz", + // "AasxToLoad": "https://eis-data.aas-voyager.com/shell-descriptors/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NGMjA0MTAxUjE0MDAvYWFz", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\210_Copy_Paste\\Sample_AAS.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\310_Plugin_DNP\\Sample_AAS.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\200_Find_Replace\\Sample_SMT.aasx", diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index 6e78ded98..bf2aaa09b 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -2288,7 +2288,7 @@ public bool SafeguardAccess( // List manipulations (single entities) // - public int MoveElementInListUpwards(List list, T entity) + public int MoveElementInListUpwards(IList list, T entity) { if (list == null || list.Count < 2 || entity == null) return -1; @@ -2301,7 +2301,7 @@ public int MoveElementInListUpwards(List list, T entity) return newndx; } - public int MoveElementInListDownwards(List list, T entity) + public int MoveElementInListDownwards(IList list, T entity) { if (list == null || list.Count < 2 || entity == null) return -1; @@ -2314,7 +2314,7 @@ public int MoveElementInListDownwards(List list, T entity) return newndx; } - public int MoveElementToTopOfList(List list, T entity) + public int MoveElementToTopOfList(IList list, T entity) { if (list == null || list.Count < 2 || entity == null) return -1; @@ -2327,7 +2327,7 @@ public int MoveElementToTopOfList(List list, T entity) return newndx; } - public int MoveElementToBottomOfList(List list, T entity) + public int MoveElementToBottomOfList(IList list, T entity) { if (list == null || list.Count < 2 || entity == null) return -1; @@ -2340,7 +2340,7 @@ public int MoveElementToBottomOfList(List list, T entity) return newndx; } - public object DeleteElementInList(List list, T entity, object alternativeReturn) + public object DeleteElementInList(IList list, T entity, object alternativeReturn) { if (list == null || entity == null) return alternativeReturn; @@ -2353,7 +2353,7 @@ public object DeleteElementInList(List list, T entity, object alternativeR return alternativeReturn; } - public int AddElementInListBefore(List list, T entity, T existing) + public int AddElementInListBefore(IList list, T entity, T existing) { if (list == null || list.Count < 1 || entity == null) return -1; @@ -2364,7 +2364,7 @@ public int AddElementInListBefore(List list, T entity, T existing) return ndx; } - public int AddElementInListAfter(List list, T entity, T existing) + public int AddElementInListAfter(IList list, T entity, T existing) { if (list == null || list.Count < 1 || entity == null) return -1; @@ -2379,7 +2379,7 @@ public int AddElementInListAfter(List list, T entity, T existing) // List manipulations (multiple entities) // - public int MoveElementsToStartingIndex(List list, List entities, int startingIndex) + public int MoveElementsToStartingIndex(IList list, List entities, int startingIndex) { // check if (list == null || list.Count < 1 || entities == null) @@ -2401,7 +2401,7 @@ public int MoveElementsToStartingIndex(List list, List entities, int st return si2; } - public int DeleteElementsInList(List list, List entities) + public int DeleteElementsInList(IList list, List entities) { // check if (list == null || list.Count < 1 || entities == null) @@ -2419,7 +2419,7 @@ public int DeleteElementsInList(List list, List entities) // public int AddElementInSmeListBefore( - List list, + IList list, T entity, T existing, bool makeUniqueIfNeeded = false) where T : Aas.ISubmodelElement @@ -2438,7 +2438,7 @@ public int AddElementInSmeListBefore( } public int AddElementInSmeListAfter( - List list, + IList list, T entity, T existing, bool makeUniqueIfNeeded = false) where T : Aas.ISubmodelElement @@ -2462,7 +2462,7 @@ public int AddElementInSmeListAfter( public void EntityListUpDownDeleteHelper( AnyUiPanel stack, ModifyRepo repo, - List list, Action> setOutputList, + IList list, Action> setOutputList, T entity, object alternativeFocus, string label = "Entities:", object nextFocus = null, PackCntChangeEventData sendUpdateEvent = null, bool preventMove = false, @@ -3029,6 +3029,9 @@ public void AddDiaryEntry(Aas.IReferable rf, DiaryEntryBase de, if (de == null) return; + // tainting + TaintedDataDef.TaintIdentifiable(rf); + // structure? if (de is DiaryEntryStructChange desc) { diff --git a/src/AasxPackageLogic/DispEditHelperCopyPaste.cs b/src/AasxPackageLogic/DispEditHelperCopyPaste.cs index 1231db1ef..8ab7e2203 100644 --- a/src/AasxPackageLogic/DispEditHelperCopyPaste.cs +++ b/src/AasxPackageLogic/DispEditHelperCopyPaste.cs @@ -739,7 +739,7 @@ public void DispSubmodelCutCopyPasteHelper( AnyUiPanel stack, ModifyRepo repo, CopyPasteBuffer cpbInternal, - List parentContainer, + IList parentContainer, T entity, Func cloneEntity, Aas.IReference smref, @@ -987,7 +987,7 @@ public void DispPlainIdentifiableCutCopyPasteHelper( AnyUiPanel stack, ModifyRepo repo, CopyPasteBuffer cpbInternal, - List parentContainer, + IList parentContainer, T entity, Func cloneEntity, string label = "Buffer:", diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index abb43ff9b..aaf24b8ee 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -939,18 +939,24 @@ public void DisplayOrEditAasEntityAasEnv( var success = false; if (ve.CdSortOrder == VisualElementEnvironmentItem.ConceptDescSortOrder.IdShort) { - env.ConceptDescriptions.Sort(new ComparerIdShort()); + var x = env.ConceptDescriptions.ToList(); + x.Sort(new ComparerIdShort()); + env.ConceptDescriptions = x; success = true; } if (ve.CdSortOrder == VisualElementEnvironmentItem.ConceptDescSortOrder.Id) { - env.ConceptDescriptions.Sort(new ComparerIdentification()); + var x = env.ConceptDescriptions.ToList(); + x.Sort(new ComparerIdentification()); + env.ConceptDescriptions = x; success = true; } if (ve.CdSortOrder == VisualElementEnvironmentItem.ConceptDescSortOrder.BySubmodel) { var cmp = env.CreateIndexedComparerCdsForSmUsage(); - env.ConceptDescriptions.Sort(cmp); + var x = env.ConceptDescriptions.ToList(); + x.Sort(cmp); + env.ConceptDescriptions = x; success = true; } @@ -1229,7 +1235,9 @@ public void DisplayOrEditAasEntitySupplementaryFile( // public void DisplayOrEditAasEntityAas( - PackageCentral.PackageCentral packages, Aas.IEnvironment env, + PackageCentral.PackageCentral packages, + AdminShellPackageEnvBase package, + Aas.IEnvironment env, Aas.IAssetAdministrationShell aas, bool editMode, AnyUiStackPanel stack, bool hintMode = false, AasxMenu superMenu = null) @@ -1490,7 +1498,42 @@ public void DisplayOrEditAasEntityAas( } return new AnyUiLambdaActionNone(); - }); + }); + + // on demand loading? + if (package is AdminShellPackageDynamicFetchEnv dynPack) + { + this.AddActionPanel( + stack, "Load missing stubs:", + repo: repo, + superMenu: superMenu, + ticketMenu: new AasxMenu() + .AddAction("stub-load-submodels", "Submodels", + "Load missing Submodels only for this AAS.") + .AddAction("stub-load-concepts", "ConceptDescriptions", + "Load missing ConceptDescriptions only for this AAS."), + ticketActionAsync: async (buttonNdx, ticket) => + { + List lrs = null; + + if (buttonNdx == 0) + lrs = aas?.FindAllSubmodelReferences().ToList(); + + + if (buttonNdx == 1) + lrs = env.FindAllSemanticIdsForAas(aas).ToList(); + + if (lrs != null) + { + var ids = lrs.Select((lr) => (lr?.Reference?.IsValid() == true) ? lr.Reference.Keys[0].Value : null).ToList(); + var fetched = await dynPack.TryFetchSpecificIds(ids); + if (fetched) + return new AnyUiLambdaActionRedrawAllElements(nextFocus: aas); + } + + return new AnyUiLambdaActionNone(); + }); + } } // Referable @@ -2352,6 +2395,72 @@ public void DisplayOrEditAasEntitySubmodelOrRef( } } + // + // + // --- Submodel Stub + // + // + + public void DisplayOrEditAasEntitySubmodelStub( + PackageCentral.PackageCentral packages, AdminShellPackageEnvBase packEnv, + Aas.IAssetAdministrationShell aas, + Aas.IReference smref, + Action setSmRefNull, + AasIdentifiableSideInfo sideInfo, + bool editMode, + AnyUiStackPanel stack, bool hintMode = false, bool checkSmt = false, + AasxMenu superMenu = null) + { + // access the on demand classes + var packOD = packEnv as AdminShellPackageDynamicFetchEnv; + + // header + this.AddGroup(stack, "Submodel stub data (Identifiable not already loaded)", this.levelColors.MainSection); + + // error? + if (packOD == null) + { + AddHintBubble(stack, true, + new HintCheck( + () => true, "Package environment does not provide on demand functionality", + severityLevel: HintCheck.Severity.High)); + } + else + { + AddActionPanel(stack, "Action:", + repo: repo, + superMenu: superMenu, + ticketMenu: new AasxMenu() + .AddAction("stub-load", "Load", + "Loads the Identifiable data from available data source.") + .AddAction("stub-load-all", "Load all", + "Loads data from available data source for all " + + "Identifiable stubs in environment."), + ticketActionAsync: async (buttonNdx, ticket) => + { + if (buttonNdx == 0) + { + var fetchedSm = await packOD.FindOrFetchIdentifiable(sideInfo?.Id); + if (fetchedSm != null) + { + return new AnyUiLambdaActionRedrawAllElements(nextFocus: fetchedSm); + } + } + + if (buttonNdx == 1) + { + var res = await packOD.TryFetchAllMissingIdentifiables(); + if (res) + { + return new AnyUiLambdaActionRedrawAllElements(nextFocus: null); + } + } + + return new AnyUiLambdaActionNone(); + }); + } + } + // // // --- Concept Description @@ -4829,7 +4938,7 @@ public bool DisplayOrEditCommonEntity( else if (entity is VisualElementAdminShell veaas) { DisplayOrEditAasEntityAas( - packages, veaas.theEnv, veaas.theAas, editMode, stack, hintMode: hintMode, + packages, veaas.thePackage, veaas.theEnv, veaas.theAas, editMode, stack, hintMode: hintMode, superMenu: superMenu); } else if (entity is VisualElementAsset veas) @@ -4867,6 +4976,15 @@ public bool DisplayOrEditCommonEntity( hintMode: hintMode, checkSmt: checkSmt, superMenu: superMenu); } + else if (entity is VisualElementSubmodelStub vesms && vesms.theSideInfo != null) + { + DisplayOrEditAasEntitySubmodelStub( + packages, vesms.thePackEnv, + aas: null, smref: null, setSmRefNull: null, + sideInfo: vesms.theSideInfo, editMode: editMode, stack: stack, + hintMode: hintMode, checkSmt: checkSmt, + superMenu: superMenu); + } else if (entity is VisualElementSubmodelElement vesme) { DisplayOrEditAasEntitySubmodelElement( diff --git a/src/AasxPackageLogic/DispEditHelperMultiElement.cs b/src/AasxPackageLogic/DispEditHelperMultiElement.cs index 8f4ae4279..afbdce59c 100644 --- a/src/AasxPackageLogic/DispEditHelperMultiElement.cs +++ b/src/AasxPackageLogic/DispEditHelperMultiElement.cs @@ -125,7 +125,7 @@ public void DispMultiElementCutCopyPasteHelper( /// public void EntityListMultipleUpDownDeleteHelper( AnyUiPanel stack, ModifyRepo repo, - List list, List entities, ListOfVisualElementBasic.IndexInfo indexInfo, + IList list, List entities, ListOfVisualElementBasic.IndexInfo indexInfo, object alternativeFocus = null, string label = "Entities:", PackCntChangeEventData sendUpdateEvent = null, bool preventMove = false, bool reFocus = false, AasxMenu superMenu = null) diff --git a/src/AasxPackageLogic/EclassUtils.cs b/src/AasxPackageLogic/EclassUtils.cs index c77198c4d..30af63a07 100644 --- a/src/AasxPackageLogic/EclassUtils.cs +++ b/src/AasxPackageLogic/EclassUtils.cs @@ -8,6 +8,7 @@ This source code may use other Open Source software components (see LICENSE.txt) */ using AdminShellNS; +using AdminShellNS.Extensions; using AngleSharp.Text; using Extensions; using Microsoft.IdentityModel.Tokens; diff --git a/src/AasxPackageLogic/EmptyListVisitor.cs b/src/AasxPackageLogic/EmptyListVisitor.cs index 61cadc22b..ce243482c 100644 --- a/src/AasxPackageLogic/EmptyListVisitor.cs +++ b/src/AasxPackageLogic/EmptyListVisitor.cs @@ -11,7 +11,6 @@ This source code may use other Open Source software components (see LICENSE.txt) */ using AdminShellNS.Extensions; -using Microsoft.IdentityModel.Tokens; using System.Collections.Generic; using System.Windows.Forms; diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index 6b260c811..aa033538e 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -53,6 +53,7 @@ public static AasxMenu CreateMainMenu() args: new AasxMenuListOfArgDefs() .Add("File", "Source filename including a path and extension.")) .AddWpfBlazor(name: "ConnectIntegrated", header: "Connect …", inputGesture: "Ctrl+Shift+I") + .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …", inputGesture: "Ctrl+Shift+E") .AddWpfBlazor(name: "Save", header: "_Save", inputGesture: "Ctrl+S") .AddWpfBlazor(name: "SaveAs", header: "_Save as …", help: "Saves current package to given file name and typr", diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 397bac1e8..d11418349 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -24,6 +24,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Security.Cryptography.Pkcs; using System.Text; using System.Threading.Tasks; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; using Aas = AasCore.Aas3_0; // ReSharper disable MethodHasAsyncOverload @@ -500,6 +501,63 @@ await val.PerformDialogue(ticket, DisplayContext, } } + if (cmd == "connectextended") + { + // start + ticket.StartExec(); + + //do + try + { + var record = new ConnectExtendedRecord(); + + var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( + ticket, DisplayContext, + "Connect AAS Repository", + record); + + if (!uiRes) + return; + + var location = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + if (location == null) + { + LogErrorToTicket(ticket, "Error building location from query selection. Aborting."); + return; + } + + // more details into container options + var containerOptions = new PackageContainerHttpRepoSubset. + PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + record); + + // load + Log.Singleton.Info($"For extended connect, loading " + + $"from {location} into container"); + + var container = await PackageContainerFactory.GuessAndCreateForAsync( + PackageCentral, + location, + location, + overrideLoadResident: true, + containerOptions: containerOptions, + runtimeOptions: PackageCentral.CentralRuntimeOptions); + + if (container == null) + Log.Singleton.Error($"Failed to load from {location}"); + else + MainWindow.UiLoadPackageWithNew(PackageCentral.MainItem, + takeOverContainer: container, onlyAuxiliary: false, indexItems: true, + storeFnToLRU: location); + + Log.Singleton.Info($"Successfully loaded {location}"); + } + catch (Exception ex) + { + LogErrorToTicket(ticket, ex, "when performing extended connect"); + } + } + if (cmd == "comparesmt") { // start diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs new file mode 100644 index 000000000..bca5f5b99 --- /dev/null +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -0,0 +1,252 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + +using AasxIntegrationBase; +using AasxOpenIdClient; +using AdminShellNS; +using AdminShellNS.DiaryData; +using Aas = AasCore.Aas3_0; +using Extensions; +using IdentityModel.Client; +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using Newtonsoft.Json; +using System.Linq; + +namespace AasxPackageLogic.PackageCentral +{ + public enum AasIdentifiableSideInfoLevel { IdOnly, IdAndMore, IdWithEndpoint }; + + /// + /// This side information helps managing Identifiables, which are not already loaded. + /// + public class AasIdentifiableSideInfo : OnDemandSideInfoBase + { + public AasIdentifiableSideInfoLevel Level = AasIdentifiableSideInfoLevel.IdOnly; + + public string Id = ""; + public string IdShort = ""; + } + + /// + /// This class provides some service functions to manage on demand list of Identifiables. + /// + /// + public class OnDemandListIdentifiable : OnDemandList where T : Aas.IIdentifiable + { + public int FindSideInfoIndexFromId(string id) + { + if (id?.HasContent() != true) + return -1; + + for (int i=0; i < this.Count(); i++) + { + var si = this.GetSideInfo(i); + if (si?.Id != null && si.Id.Trim() == id.Trim()) + return i; + } + + return -1; + } + } + + /// + /// This class creates a package env, which can handle dynamic loading of elements + /// + public class AasOnDemandEnvironment : Aas.IEnvironment + { + /// + /// Asset administration shell + /// + public IList AssetAdministrationShells { get; set; } + + /// + /// Submodel + /// + public IList Submodels { get; set; } + + /// + /// Concept description + /// + public IList ConceptDescriptions { get; set; } + + /// + /// Iterate over AssetAdministrationShells, if set, and otherwise return an empty enumerable. + /// + public IEnumerable OverAssetAdministrationShellsOrEmpty() + { + return AssetAdministrationShells + ?? System.Linq.Enumerable.Empty(); + } + + [JsonIgnore] + private DiaryDataDef _diaryData = new DiaryDataDef(); + + [JsonIgnore] + public DiaryDataDef DiaryData { get { return _diaryData; } } + + /// + /// Iterate over Submodels, if set, and otherwise return an empty enumerable. + /// + public IEnumerable OverSubmodelsOrEmpty() + { + return Submodels + ?? System.Linq.Enumerable.Empty(); + } + + /// + /// Iterate over ConceptDescriptions, if set, and otherwise return an empty enumerable. + /// + public IEnumerable OverConceptDescriptionsOrEmpty() + { + return ConceptDescriptions + ?? System.Linq.Enumerable.Empty(); + } + + /// + /// Iterate over all the class instances referenced from this instance + /// without further recursion. + /// + public IEnumerable DescendOnce() + { + if (AssetAdministrationShells != null) + { + foreach (var anItem in AssetAdministrationShells) + { + yield return anItem; + } + } + + if (Submodels != null) + { + foreach (var anItem in Submodels) + { + yield return anItem; + } + } + + if (ConceptDescriptions != null) + { + foreach (var anItem in ConceptDescriptions) + { + yield return anItem; + } + } + } + + /// + /// Iterate recursively over all the class instances referenced from this instance. + /// + public IEnumerable Descend() + { + if (AssetAdministrationShells != null) + { + foreach (var anItem in AssetAdministrationShells) + { + yield return anItem; + + // Recurse + foreach (var anotherItem in anItem.Descend()) + { + yield return anotherItem; + } + } + } + + if (Submodels != null) + { + foreach (var anItem in Submodels) + { + yield return anItem; + + // Recurse + foreach (var anotherItem in anItem.Descend()) + { + yield return anotherItem; + } + } + } + + if (ConceptDescriptions != null) + { + foreach (var anItem in ConceptDescriptions) + { + yield return anItem; + + // Recurse + foreach (var anotherItem in anItem.Descend()) + { + yield return anotherItem; + } + } + } + } + + /// + /// Accept the to visit this instance + /// for double dispatch. + /// + public void Accept(Visitation.IVisitor visitor) + { + visitor.VisitEnvironment(this); + } + + /// + /// Accept the visitor to visit this instance for double dispatch + /// with the . + /// + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context) + { + visitor.VisitEnvironment(this, context); + } + + /// + /// Accept the to transform this instance + /// for double dispatch. + /// + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformEnvironment(this); + } + + /// + /// Accept the to visit this instance + /// for double dispatch with the . + /// + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context) + { + return transformer.TransformEnvironment(this, context); + } + + public AasOnDemandEnvironment( + IList assetAdministrationShells = null, + IList submodels = null, + IList conceptDescriptions = null) + { + AssetAdministrationShells = assetAdministrationShells; + Submodels = submodels; + ConceptDescriptions = conceptDescriptions; + } + + } + +} diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs new file mode 100644 index 000000000..b3882ff48 --- /dev/null +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -0,0 +1,313 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + +using AasxIntegrationBase; +using AasxOpenIdClient; +using AdminShellNS; +using Aas = AasCore.Aas3_0; +using Extensions; +using IdentityModel.Client; +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using AdminShellNS.DiaryData; +using System.Text.Json; + +namespace AasxPackageLogic.PackageCentral +{ + /// + /// This class creates a package env, which can handle dynamic loading of elements + /// + public class AdminShellPackageDynamicFetchEnv : AdminShellPackageEnvBase + { + protected PackCntRuntimeOptions _runtimeOptions = null; + + protected Uri _defaultRepoBaseUri = null; + + protected Dictionary _thumbStreamPerAasId = new Dictionary(); + + public AdminShellPackageDynamicFetchEnv( + PackCntRuntimeOptions runtimeOptions = null, + Uri baseUri = null) : base() + { + _runtimeOptions = runtimeOptions; + _defaultRepoBaseUri = baseUri; + } + + public override bool IsOpen + { + get + { + if (AasEnv == null) + return false; + var someIdf = AasEnv.AssetAdministrationShellCount() > 0 + || AasEnv.SubmodelCount() > 0 + || AasEnv.ConceptDescriptionCount() > 0; + return someIdf && _defaultRepoBaseUri != null; + } + } + + public async Task FindOrFetchIdentifiable(string id) + { + // access + if (id?.HasContent() != true + || !(_aasEnv is AasOnDemandEnvironment odEnv)) + return null; + + // find existing? + Aas.IIdentifiable idf = _aasEnv?.FindAasById(id); + idf = idf ?? _aasEnv?.FindSubmodelById(id); + idf = idf ?? _aasEnv?.FindConceptDescriptionById(id); + if (idf != null) + return idf; + + // try locate id in Submodels? + var sms = _aasEnv?.Submodels as OnDemandListIdentifiable; + var smndx = sms?.FindSideInfoIndexFromId(id); + if (smndx.HasValue && smndx.Value >= 0) + { + // directly use id to fetch Identifiable + Aas.IIdentifiable res = null; + + // build the location + var loc = PackageContainerHttpRepoSubset.BuildUriForSubmodel(_defaultRepoBaseUri, id); + if (loc == null) + return null; + + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: loc, + allowFakeResponses: _runtimeOptions?.AllowFakeResponses ?? false, + lambdaDownloadDone: (ms, contentFn) => + { + try + { + // load? + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + var sm = Jsonization.Deserialize.SubmodelFrom(node); + + // replace, side info will go null + sms[smndx.Value] = sm; + + // ok + res = sm; + } + catch (Exception ex) + { + _runtimeOptions?.Log?.Error(ex, "Parsing on demand loaded Submodel"); + } + }); + + return res; + } + + // nope + return null; + } + + public async Task TryFetchSpecificIds(IEnumerable ids) + { + var someFetched = false; + if (false) + { + // buggy + await Parallel.ForEachAsync( + ids, new ParallelOptions() { MaxDegreeOfParallelism = 10 }, + async (id, token) => + { + var idf = await FindOrFetchIdentifiable(id); + if (idf != null) + someFetched = true; + }); + } + else + { + // not optimal + foreach (var id in ids) + { + var idf = await FindOrFetchIdentifiable(id); + if (idf != null) + someFetched = true; + } + } + return someFetched; + } + + protected async Task TryFetchAllMissingOf(object listOfIdf) where T : Aas.IIdentifiable + { + var list = listOfIdf as OnDemandListIdentifiable; + if (list == null) + return false; + + var idsToFetch = new List(); + for (int i=0; i TryFetchAllMissingIdentifiables( + bool allAas = true, + bool allSubmodels = true, + bool allCDs = true) + { + var res = false; + + if (allAas) res = res || await TryFetchAllMissingOf(_aasEnv?.AssetAdministrationShells); + if (allSubmodels) res = res || await TryFetchAllMissingOf(_aasEnv?.Submodels); + if (allCDs) res = res || await TryFetchAllMissingOf(_aasEnv?.ConceptDescriptions); + + return res; + } + + protected async Task TrySaveAllTaintedIdentifiablesOf( + object listOfIdf, + Func lambdaBuildRessource, + bool clearTaintedFlags = true) where T : Aas.IIdentifiable + { + var list = listOfIdf as OnDemandListIdentifiable; + if (list == null) + return 0; + + var count = 0; + for (int i = 0; i < list.Count(); i++) + { + // Note: elements with side info are not relevant, as only fetched elements + // need to be written back .. + var si = list.GetSideInfo(i); + if (si != null) + continue; + + // access + var idf = list[i]; + if (idf == null) + // surprising :-/ + continue; + + // tainted? (in doubt, yes) + var tidf = idf as ITaintedData; + if (tidf?.TaintedData != null && tidf.TaintedData.Tainted == null) + continue; + + // try save, need a REST ressource + var uri = lambdaBuildRessource(_defaultRepoBaseUri, idf.Id); + if (uri == null) + continue; + + // serialize to memory stream + var res = false; + using (var ms = new MemoryStream()) + { + var jsonWriterOptions = new System.Text.Json.JsonWriterOptions + { + Indented = false + }; + + using (var wr = new System.Text.Json.Utf8JsonWriter(ms, jsonWriterOptions)) + { + // serialize + Jsonization.Serialize.ToJsonObject(idf).WriteTo(wr); + wr.Flush(); + ms.Flush(); + + // prepare for reading again + ms.Seek(0, SeekOrigin.Begin); + + // write + var res2 = await PackageHttpDownloadUtil.HttpPutFromMemoryStream( + ms, + destUri: uri, + allowFakeResponses: _runtimeOptions?.AllowFakeResponses ?? false); + if (!res2) + { + _runtimeOptions?.Log?.Error("Save of modified Identifiable returned error for id={0} at {1}", + idf.Id, + uri.ToString()); + } + else + res = true; + } + } + + // clear the tainted flag + if (res && clearTaintedFlags && tidf?.TaintedData != null) + tidf.TaintedData.Tainted = null; + } + + return count; + } + + public async Task TrySaveAllTaintedIdentifiables( + bool allAas = true, + bool allSubmodels = true, + bool allCDs = true) + { + var count = 0; + + if (allAas) count += await TrySaveAllTaintedIdentifiablesOf( + _aasEnv?.AssetAdministrationShells, + (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForAAS(defBase, id)); + + if (allSubmodels) count += await TrySaveAllTaintedIdentifiablesOf( + _aasEnv?.Submodels, + (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForSubmodel(defBase, id)); + + if (allCDs) count += await TrySaveAllTaintedIdentifiablesOf( + _aasEnv?.ConceptDescriptions, + (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForCD(defBase, id)); + + return count; + } + + public void AddThumbnail(string aasId, byte[] content) + { + if (aasId?.HasContent() != true) + return; + if (_thumbStreamPerAasId.ContainsKey(aasId)) + _thumbStreamPerAasId[aasId] = content; + else + _thumbStreamPerAasId.Add(aasId, content); + } + + public byte[] GetThumbnail(string aasId) + { + if (aasId?.HasContent() != true || !_thumbStreamPerAasId.ContainsKey(aasId)) + return null; + return _thumbStreamPerAasId[aasId]; + } + + public override Stream GetThumbnailStreamFromAasOrPackage(string aasId) + { + // check for content + var content = GetThumbnail(aasId); + if (content == null) + return null; + + // return stream + var ms = new MemoryStream(); + ms.Write(content, 0, content.Length); + ms.Seek(0, SeekOrigin.Begin); + return ms; + } + } + +} diff --git a/src/AasxPackageLogic/PackageCentral/OnDemandList.cs b/src/AasxPackageLogic/PackageCentral/OnDemandList.cs new file mode 100644 index 000000000..35d43d68d --- /dev/null +++ b/src/AasxPackageLogic/PackageCentral/OnDemandList.cs @@ -0,0 +1,152 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + +using AasxIntegrationBase; +using AdminShellNS; +using AdminShellNS.DiaryData; +using Aas = AasCore.Aas3_0; +using Extensions; +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using Newtonsoft.Json; +using System.Collections; + +namespace AasxPackageLogic.PackageCentral +{ + /// + /// Base class for the side information describing the (status of the) data in + /// the collection. Intended to be derived and stuffed with information. + /// + public class OnDemandSideInfoBase + { + } + + /// + /// Pair of side information and actual data element. + /// + public class OnDemandListItem + { + public OnDemandSideInfoBase SideInfo; + public T Data; + } + + /// + /// Test + /// + public class OnDemandList : IList where V : OnDemandSideInfoBase + { + protected List> _items = new List>(); + + public T this[int index] { + get => _items[index].Data; + set { + _items[index] = new OnDemandListItem() { Data = value }; + } + } + + int ICollection.Count => _items.Count; + + bool ICollection.IsReadOnly => false; + + void ICollection.Add(T item) + { + _items.Add(new OnDemandListItem() { Data = item }); + } + + public void Add(T item, V sideInfo) + { + _items.Add(new OnDemandListItem() { SideInfo = sideInfo, Data = item }); + } + + void ICollection.Clear() + { + _items.Clear(); + } + + bool ICollection.Contains(T item) + { + foreach (var x in _items) + if ((object) x.Data == (object) item) + return true; + return false; + } + + void ICollection.CopyTo(T[] array, int arrayIndex) + { + for (int i = 0; i < _items.Count; i++) + array[arrayIndex + i] = _items[i].Data.Copy(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + foreach (var it in _items) + yield return it.Data; + } + + IEnumerator IEnumerable.GetEnumerator() + { + foreach (var it in _items) + yield return it.Data; + } + + int IList.IndexOf(T item) + { + for (int i = 0; i < _items.Count; i++) + if ((object)item == (object)_items[i].Data) + return i; + return -1; + } + + void IList.Insert(int index, T item) + { + _items.Insert(index, new OnDemandListItem() { Data = item }); + } + + bool ICollection.Remove(T item) + { + for (int i = 0; i < _items.Count; i++) + if ((object)item == (object)_items[i].Data) + { + _items.RemoveAt(i); + return true; + } + return false; + } + + void IList.RemoveAt(int index) + { + _items.RemoveAt(index); + } + + public V GetSideInfo(int index) + { + if (index < 0 || index >= _items.Count) + return null; + return _items[index].SideInfo as V; + } + + public V SetSideInfo(int index, V sideInfo) + { + if (index < 0 || index >= _items.Count) + return null; + _items[index].SideInfo = sideInfo; + return sideInfo; + } + } + +} diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs index 72616f493..2c15fdd0f 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs @@ -65,6 +65,11 @@ public delegate AnyUiMessageBoxResult ShowMessageDelegate( string content, string text, string caption, AnyUiMessageBoxButton buttons = 0); public ShowMessageDelegate ShowMesssageBox; + + /// + /// Allow simulating a repo by predefined JSON web responses. + /// + public bool AllowFakeResponses = false; } /// @@ -357,6 +362,7 @@ public virtual void BackupInDir(string backupDir, int maxFiles, BackupType backu public virtual async Task LoadFromSourceAsync( string fullItemLocation, + PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) { await Task.Yield(); diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs index 3857a3eb3..304b74092 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs @@ -214,6 +214,7 @@ public static async Task GuessAndCreateForAsync( if (guess.GuessedType == typeof(PackageContainerHttpRepoSubset)) { + runtimeOptions.AllowFakeResponses = false; var cnt = await PackageContainerHttpRepoSubset.CreateAndLoadAsync( packageCentral, location, fullItemLocation, overrideLoadResident, takeOver: takeOver, diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 39e598bef..49f5a1203 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -24,6 +24,8 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; +using AasxPackageExplorer; +using AnyUi; namespace AasxPackageLogic.PackageCentral { @@ -80,7 +82,6 @@ public PackageContainerHttpRepoSubset(CopyMode mode, PackageContainerBase other, ContainerOptions = containerOptions; } - public static async Task CreateAndLoadAsync( PackageCentral packageCentral, string location, @@ -97,7 +98,7 @@ public static async Task CreateAndLoadAsync( res.ContainerList = containerList; if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) - await res.LoadFromSourceAsync(fullItemLocation, runtimeOptions); + await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions); return res; } @@ -154,41 +155,182 @@ public static bool IsValidUriForSingleCD(string location) return m.Success; } + public static bool IsValidUriForQuery(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/aaspe-query/(.{1,9999})$", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + public static bool IsValidUriAnyMatch(string location) { return IsValidUriForAllAAS(location) || IsValidUriForSingleAAS(location) || IsValidUriForSingleSubmodel(location) - || IsValidUriForSingleCD(location); + || IsValidUriForSingleCD(location) + || IsValidUriForQuery(location); + } + + public static Uri GetBaseUri(string location) + { + // try an explicit search for known parts of ressources + // (preserves scheme, host and leading pathes) + var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/conceptdescription)"); + if (m.Success) + return new Uri(m.Groups[1].ToString() + "/"); + + // just go to the first slash + var p0 = location.IndexOf("//"); + if (p0 > 0) + { + var p = location.IndexOf('/', p0 + 2); + if (p > 0) + { + return new Uri(location.Substring(0, p) + "/"); + } + } + + // go to error + return null; + } + + //public static string CombineUri (string uri1, string uri2) + //{ + // var res = "" + uri1; + // if (uri2?.HasContent() == true) + // { + // if (!res.EndsWith("/")) + // res += "/"; + // res += uri2; + // } + // return res; + //} + + public static Uri CombineUri(Uri baseUri, string relativeUri) + { + if (baseUri == null || relativeUri?.HasContent() != true) + return null; + + if (Uri.TryCreate(baseUri, relativeUri, out var res)) + return res; + + return null; + } + + public static Uri BuildUriForAAS(Uri baseUri, string id, bool encryptIds = true) + { + // access + if (id?.HasContent() != true) + return null; + + // try combine + var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"shells/{smidenc}"); + } + + public static Uri BuildUriForAasThumbnail(Uri baseUri, string id, bool encryptIds = true) + { + // access + if (id?.HasContent() != true) + return null; + + // try combine + var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"shells/{smidenc}/asset-information/thumbnail"); + } + + public static Uri BuildUriForSubmodel(Uri baseUri, string id, bool encryptIds = true) + { + // access + if (id?.HasContent() != true) + return null; + + // try combine + var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"submodels/{smidenc}"); + } + + public static Uri BuildUriForCD(Uri baseUri, string id, bool encryptIds = true) + { + // access + if (id?.HasContent() != true) + return null; + + // try combine + var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"concept-descriptions/{smidenc}"); + } + + /// + /// Note: this is an AASPE specific, proprietary extension. + /// This REST ressource does not exist in the official specification! + /// + public static Uri BuildUriForQuery(Uri baseUri, string query) + { + // access + if (query?.HasContent() != true) + return null; + + // try combine + var queryEnc = AdminShellUtil.Base64Encode(query); + return CombineUri(baseUri, $"aaspe-query/{queryEnc}"); + } + + public static Uri BuildUriForSubmodel(Uri baseUri, Aas.IReference submodelRef) + { + // access + if (baseUri == null || submodelRef?.IsValid() != true + || submodelRef.Count() != 1 || submodelRef.Keys[0].Type != KeyTypes.Submodel) + return null; + + // pass on + return BuildUriForSubmodel(baseUri, submodelRef.Keys[0].Value); } public override async Task LoadFromSourceAsync( string fullItemLocation, + PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) { //PackageHttpDownloadUtil.TryLoadFakeRequests(Assembly.GetExecutingAssembly(), // "AasxPackageLogic.Resources.PackageContainerFakeAnswers.json"); - var allowFakeResponses = true; + var allowFakeResponses = runtimeOptions?.AllowFakeResponses ?? false; - var baseUri = PackageHttpDownloadUtil.GetBaseUri(fullItemLocation); + var baseUri = GetBaseUri(fullItemLocation); + + // for the time being, make sure, we have the correct list implementations + var prepAas = new OnDemandListIdentifiable(); + var prepSM = new OnDemandListIdentifiable(); + var prepCD = new OnDemandListIdentifiable(); // integrate in a fresh environment // TODO: new kind of environment - var env = (Aas.IEnvironment) new Aas.Environment(); + var env = (Aas.IEnvironment) new AasOnDemandEnvironment(); + + // already set structure to use some convenience functions + env.AssetAdministrationShells = prepAas; + env.Submodels = prepSM; + env.ConceptDescriptions = prepCD; + + // also the package "around" + var dynPack = new AdminShellPackageDynamicFetchEnv(runtimeOptions, baseUri); + + // get the record data + var record = (containerOptions as PackageContainerHttpRepoSubsetOptions)?.Record; // start with AAS? if (IsValidUriForSingleAAS(fullItemLocation)) { - await PackageHttpDownloadUtil.DownloadToMemoryStream( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, - lambdaDownloadDone: (ms) => + lambdaDownloadDone: (ms, contentFn) => { try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - env.Add(Jsonization.Deserialize.AssetAdministrationShellFrom(node)); + prepAas.Add(Jsonization.Deserialize.AssetAdministrationShellFrom(node), null); } catch (Exception ex) { runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); @@ -199,15 +341,15 @@ await PackageHttpDownloadUtil.DownloadToMemoryStream( // start with Submodel? if (IsValidUriForSingleSubmodel(fullItemLocation)) { - await PackageHttpDownloadUtil.DownloadToMemoryStream( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, - lambdaDownloadDone: (ms) => + lambdaDownloadDone: (ms, contentFn) => { try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - env.Add(Jsonization.Deserialize.SubmodelFrom(node)); + prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); } catch (Exception ex) { @@ -219,15 +361,15 @@ await PackageHttpDownloadUtil.DownloadToMemoryStream( // start with CD? if (IsValidUriForSingleCD(fullItemLocation)) { - await PackageHttpDownloadUtil.DownloadToMemoryStream( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, - lambdaDownloadDone: (ms) => + lambdaDownloadDone: (ms, contentFn) => { try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - env.Add(Jsonization.Deserialize.ConceptDescriptionFrom(node)); + prepCD.Add(Jsonization.Deserialize.ConceptDescriptionFrom(node), null); } catch (Exception ex) { @@ -237,26 +379,68 @@ await PackageHttpDownloadUtil.DownloadToMemoryStream( } // start auto-load missing Submodels? - if (true) + if (record?.AutoLoadSubmodels ?? false) foreach (var lr in env.FindAllSubmodelReferences(onlyNotExisting: true)) - await PackageHttpDownloadUtil.DownloadToMemoryStream( - sourceUri: PackageHttpDownloadUtil.BuildUriForSubmodel(baseUri, lr.Reference), - allowFakeResponses: allowFakeResponses, - lambdaDownloadDone: (ms) => + { + if (record?.AutoLoadOnDemand ?? true) { - try + // side info level 1 + prepSM.Add(null, new AasIdentifiableSideInfo() { + Level = AasIdentifiableSideInfoLevel.IdOnly, + Id = lr.Reference.Keys[0].Value + }); + } + else + { + // no side info => full element + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: BuildUriForSubmodel(baseUri, lr.Reference), + allowFakeResponses: allowFakeResponses, + lambdaDownloadDone: (ms, contentFn) => { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - env.Add(Jsonization.Deserialize.SubmodelFrom(node)); - } - catch (Exception ex) + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + env.Add(Jsonization.Deserialize.SubmodelFrom(node)); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing auto-loaded Submodel"); + } + }); + } + } + + // start auto-load missing thumbnails? + if (true) + foreach (var aas in env.AllAssetAdministrationShells()) + { + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: BuildUriForAasThumbnail(baseUri, aas.Id), + allowFakeResponses: allowFakeResponses, + lambdaDownloadDone: (ms, contentFn) => { - runtimeOptions?.Log?.Error(ex, "Parsing auto-loaded Submodel"); - } - }); - - // prototypic!! - Env = new AdminShellPackageFileBasedEnv(); + try + { + dynPack.AddThumbnail(aas.Id, ms.ToArray()); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Managing auto-loaded tumbnail"); + } + }); + } + + // remove, what is not need + if (env.AssetAdministrationShellCount() < 1) + env.AssetAdministrationShells = null; + if (env.SubmodelCount() < 1) + env.Submodels = null; + if (env.ConceptDescriptionCount() < 1) + env.ConceptDescriptions = null; + + // commit + Env = dynPack; Env.SetEnvironment(env); } @@ -277,6 +461,436 @@ public override async Task SaveToSourceAsync(string saveAsNewFileName = null, PackCntRuntimeOptions runtimeOptions = null, bool doNotRememberLocation = false) { + // access + if (!(Env is AdminShellPackageDynamicFetchEnv dynPack) + || dynPack.IsOpen != true) + { + runtimeOptions.Log?.Error("Cannot save environment as pre-conditions are not met."); + return; + } + + // refer to dynPack + await dynPack.TrySaveAllTaintedIdentifiables(); + } + + // + // UI + // + + public class ConnectExtendedRecord + { + public string BaseAddress = "https://eis-data.aas-voyager.com/"; + + public bool GetAllAas; + + public bool GetSingleAas = true; + public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; + + public bool GetSingleSubmodel; + public string SmId; + + public bool GetSingleCD; + public string CdId; + + public bool ExecuteQuery; + public string QueryScript; + + public bool AutoLoadSubmodels = true; + public bool AutoLoadCds = true; + public bool AutoLoadThumbnails = true; + public bool AutoLoadOnDemand = true; + public bool EncryptIds = true; + public bool StayConnected; + + public void SetQueryChoices(int choice) + { + GetAllAas = (choice == 1); + GetSingleAas = (choice == 2); + GetSingleSubmodel = (choice == 3); + GetSingleCD = (choice == 4); + ExecuteQuery = (choice == 5); + } + } + + public class PackageContainerHttpRepoSubsetOptions : PackageContainerOptionsBase + { + public PackageContainerHttpRepoSubsetOptions( + PackageContainerOptionsBase baseOpt, + ConnectExtendedRecord record) + { + LoadResident = baseOpt.LoadResident; + StayConnected = baseOpt.StayConnected; + UpdatePeriod = baseOpt.UpdatePeriod; + Record = record; + } + + public ConnectExtendedRecord Record; + } + + public static string BuildLocationFrom(ConnectExtendedRecord record) + { + // access + if (record == null || record.BaseAddress?.HasContent() != true) + return null; + + var baseUri = new Uri(record.BaseAddress); + + // All AAS? + if (record.GetAllAas) + { + + } + + // Single AAS? + if (record.GetSingleAas) + { + var uri = BuildUriForAAS(baseUri, record.AasId, encryptIds: record.EncryptIds); + return uri.ToString(); + } + + // Single Submodel? + if (record.GetSingleSubmodel) + { + var uri = BuildUriForSubmodel(baseUri, record.SmId, encryptIds: record.EncryptIds); + return uri.ToString(); + } + + // Single CD? + if (record.GetSingleCD) + { + var uri = BuildUriForCD(baseUri, record.CdId, encryptIds: record.EncryptIds); + return uri.ToString(); + } + + // Query? + if (record.ExecuteQuery) + { + var uri = BuildUriForQuery(baseUri, record.QueryScript); + return uri.ToString(); + } + + // nope + return null; + } + + public static async Task PerformConnectExtendedDialogue( + AasxMenuActionTicket ticket, + AnyUiContextBase displayContext, + string caption, + ConnectExtendedRecord record) + { + // access + if (displayContext == null || caption?.HasContent() != true || record == null) + return false ; + + // if a target file is given, a headless operation occurs +#if __later + if (ticket != null && ticket["Target"] is string targetFn) + { + var exportFmt = -1; + var targExt = System.IO.Path.GetExtension(targetFn).ToLower(); + if (targExt == ".txt") + exportFmt = 0; + if (targExt == ".xlsx") + exportFmt = 1; + if (exportFmt < 0) + { + MainWindowLogic.LogErrorToTicketStatic(ticket, null, + $"For operation '{caption}', the target format could not be " + + $"determined by filename '{targetFn}'. Aborting."); + return; + } + + try + { + WriteTargetFile(exportFmt, targetFn); + } + catch (Exception ex) + { + MainWindowLogic.LogErrorToTicketStatic(ticket, ex, + $"While performing '{caption}'"); + return; + } + + // ok + Log.Singleton.Info("Performed '{0}' and writing report to '{1}'.", + caption, targetFn); + return; + } +#endif + + // arguments by reflection + ticket?.ArgValue?.PopulateObjectFromArgs(record); + + // reserve some states for the inner viewing routine + bool wrap = false; + + // ok, go on .. + var uc = new AnyUiDialogueDataModalPanel(caption); + uc.ActivateRenderPanel(record, + disableScrollArea: false, + dialogButtons: AnyUiMessageBoxButton.OK, + extraButtons: new[] { "A", "B" }, + renderPanel: (uci) => + { + // create panel + var panel = new AnyUiStackPanel(); + var helper = new AnyUiSmallWidgetToolkit(); + + var g = helper.AddSmallGrid(25, 2, new[] { "120:", "*" }, + padding: new AnyUiThickness(0, 5, 0, 5), + margin: new AnyUiThickness(10, 0, 30, 0)); + + panel.Add(g); + + // dynamic rows + int row = 0; + + // Base address + helper.AddSmallLabelTo(g, row, 0, content: "Base address:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row, 1, + text: $"{record.BaseAddress}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.BaseAddress = s; }); + row++; + + // All AASes + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get all AAS", + isChecked: record.GetAllAas, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => { + if ((bool) o) + record.SetQueryChoices(1); + else + record.GetAllAas = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + row++; + + // Single AAS + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get single AAS", + isChecked: record.GetSingleAas, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => { + if ((bool)o) + record.SetQueryChoices(2); + else + record.GetSingleAas = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + helper.AddSmallLabelTo(g, row + 1, 0, content: "AAS.Id:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.AasId}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.AasId = s; }); + + row += 2; + + // Single Submodel + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get single Submodel", + isChecked: record.GetSingleSubmodel, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => { + if ((bool)o) + record.SetQueryChoices(3); + else + record.GetSingleSubmodel = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + helper.AddSmallLabelTo(g, row + 1, 0, content: "Submodel.Id:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.SmId}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.SmId = s; }); + + row += 2; + + // Single CD + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get single ConceptDescription (CD)", + isChecked: record.GetSingleCD, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => { + if ((bool)o) + record.SetQueryChoices(4); + else + record.GetSingleCD = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + helper.AddSmallLabelTo(g, row + 1, 0, content: "CD.Id:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.CdId}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.CdId = s; }); + + row += 2; + + // Query + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get by query definition", + isChecked: record.ExecuteQuery, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => { + if ((bool)o) + record.SetQueryChoices(5); + else + record.ExecuteQuery = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + helper.AddSmallLabelTo(g, row + 1, 0, content: "Query:", + verticalAlignment: AnyUiVerticalAlignment.Top, + verticalContentAlignment: AnyUiVerticalAlignment.Top); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.QueryScript}", + verticalAlignment: AnyUiVerticalAlignment.Stretch, + verticalContentAlignment: AnyUiVerticalAlignment.Top, + textWrap: AnyUiTextWrapping.Wrap, + fontSize: 0.7, + multiLine: true), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch, + minHeight: 120), + (s) => { record.QueryScript = s; }); + + row += 2; + + // Auto load Submodels + + helper.AddSmallLabelTo(g, row, 0, content: "For above:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Auto-load Submodels", + isChecked: record.AutoLoadSubmodels, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.AutoLoadSubmodels = b; }); + + row++; + + // Auto load Submodels + + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Auto-load ConceptDescriptions", + isChecked: record.AutoLoadCds, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.AutoLoadCds = b; }); + + row++; + + // Auto load Thumbnails + + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Auto-load thumbnail for every AAS", + isChecked: record.AutoLoadThumbnails, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.AutoLoadThumbnails = b; }); + + row++; + + // Auto load on demand + + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Mark auto-loaded elements for on-demand loading", + isChecked: record.AutoLoadOnDemand, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.AutoLoadOnDemand = b; }); + + row++; + + // Encrypt IDs + + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Encrypt Ids (needs to be checked, unless encrypted Ids are provided)", + isChecked: record.EncryptIds, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.EncryptIds = b; }); + + row++; + + // Stay connected + + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Stay connected (will receive events)", + isChecked: record.StayConnected, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.StayConnected = b; }); + + row++; + + // give back + return g; + }); + + if (!(await displayContext.StartFlyoverModalAsync(uc))) + return false; + + // ok + return true; } } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs index 0841b2115..9e83f5561 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs @@ -82,7 +82,7 @@ public static async Task CreateAndLoadAsync( packageCentral, location, containerOptions); if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) - await res.LoadFromSourceAsync(fullItemLocation, runtimeOptions); + await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions); return res; } @@ -110,6 +110,7 @@ public override string ToString() public override async Task LoadFromSourceAsync( string fullItemLocation, + PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) { // check extension @@ -246,7 +247,9 @@ public override async Task LoadResidentIfPossible(string fullItemLocation) try { if (!IsOpen && Location.HasContent()) - await LoadFromSourceAsync(fullItemLocation, PackageCentral?.CentralRuntimeOptions); + await LoadFromSourceAsync(fullItemLocation, + // Note: container options missing?! + runtimeOptions: PackageCentral?.CentralRuntimeOptions); OnPropertyChanged("VisualIsLoaded"); } catch (Exception ex) diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs index d5a5e57ec..5957a416c 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs @@ -93,7 +93,7 @@ public static async Task CreateAndLoadAsync( res.ContainerList = containerList; if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) - await res.LoadFromSourceAsync(fullItemLocation, runtimeOptions); + await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions); return res; } @@ -302,6 +302,7 @@ await file.WriteAsync(buffer, 0, bytesRead, public override async Task LoadFromSourceAsync( string fullItemLocation, + PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) { // buffer to temp file diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs index 513fc76b0..52e978418 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs @@ -91,7 +91,7 @@ public static async Task CreateAndLoadAsync( packageCentral, location, containerOptions); if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) - await res.LoadFromSourceAsync(fullItemLocation, runtimeOptions); + await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions); return res; } @@ -207,6 +207,7 @@ public static IEnumerable EnumerateUserFiles(string searchPattern = null public override async Task LoadFromSourceAsync( string fullItemLocation, + PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) { // check extension @@ -350,7 +351,9 @@ public override async Task LoadResidentIfPossible(string fullItemLocation) try { if (!IsOpen && Location.HasContent()) - await LoadFromSourceAsync(fullItemLocation, PackageCentral?.CentralRuntimeOptions); + await LoadFromSourceAsync(fullItemLocation, + // Note: container options missing?! + runtimeOptions: PackageCentral?.CentralRuntimeOptions); OnPropertyChanged("VisualIsLoaded"); } catch (Exception ex) diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index a83a9973d..251a8c778 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -26,6 +26,9 @@ This source code may use other Open Source software components (see LICENSE.txt) using Newtonsoft.Json; using static AasxPredefinedConcepts.ConceptModel.ConceptModelZveiTechnicalData; using System.Text.RegularExpressions; +using System.Net.Http.Headers; +using System.Text; +using AasCore.Aas3_0; namespace AasxPackageLogic.PackageCentral { @@ -78,9 +81,9 @@ private static OpenIdClientInstance.UiLambdaSet GenerateUiLambdaSet(PackCntRunti return res; } - public static async Task DownloadToMemoryStream( + public static async Task HttpGetToMemoryStream( Uri sourceUri, - Action lambdaDownloadDone, + Action lambdaDownloadDone, PackCntRuntimeOptions runtimeOptions = null, PackageContainerListBase containerList = null, bool allowFakeResponses = false) @@ -101,14 +104,11 @@ public static async Task DownloadToMemoryStream( writer.Flush(); memStream.Flush(); memStream.Seek(0, SeekOrigin.Begin); - lambdaDownloadDone?.Invoke(memStream); + lambdaDownloadDone?.Invoke(memStream, "content.json"); return; } } - // TODO: debug, remove!! - return; - // read via HttpClient (uses standard proxies) var handler = new HttpClientHandler(); handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; @@ -121,7 +121,7 @@ public static async Task DownloadToMemoryStream( var requestPath = sourceUri.PathAndQuery; // Log - runtimeOptions?.Log?.Info($"HttpClient() with base-address {client.BaseAddress} " + + runtimeOptions?.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + $"and request {requestPath} .. "); // Token existing? @@ -270,7 +270,7 @@ await memStream.WriteAsync(buffer, 0, bytesRead, // execute lambda memStream.Flush(); memStream.Seek(0, SeekOrigin.Begin); - lambdaDownloadDone?.Invoke(memStream); + lambdaDownloadDone?.Invoke(memStream, contentFn); } } else @@ -281,62 +281,114 @@ await memStream.WriteAsync(buffer, 0, bytesRead, } } - public static Uri GetBaseUri(string location) + public static async Task HttpPutFromMemoryStream( + MemoryStream ms, + Uri destUri, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null, + bool allowFakeResponses = false) { - // try an explicit search for known parts of ressources - // (preserves scheme, host and leading pathes) - var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/conceptdescription)"); - if (m.Success) - return new Uri(m.Groups[1].ToString() + "/"); - - // just go to the first slash - var p0 = location.IndexOf("//"); - if (p0 > 0) + // access + if (ms == null || destUri == null) + return false; + + // for the time being: fake responses -> always return true + if (allowFakeResponses) + return true; + + // read via HttpClient (uses standard proxies) + var handler = new HttpClientHandler(); + handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; + + // new http client + var client = new HttpClient(handler); + + client.BaseAddress = new Uri(destUri.GetLeftPart(UriPartial.Authority)); + var requestPath = destUri.PathAndQuery; + + // Log + runtimeOptions?.Log?.Info($"HttpClient PUT() with base-address {client.BaseAddress} " + + $"and request {requestPath} .. "); + + // Token existing? + var clhttp = containerList as PackageContainerListHttpRestBase; + var oidc = clhttp?.OpenIdClient; + if (oidc == null) + { + runtimeOptions?.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (clhttp != null && OpenIDClient.email != "") + { + clhttp.OpenIdClient = new OpenIdClientInstance(); + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + oidc = clhttp.OpenIdClient; + } + } + if (oidc != null) { - var p = location.IndexOf('/', p0 + 2); - if (p > 0) + if (oidc.token != "") { - return new Uri(location.Substring(0, p) + "/"); + runtimeOptions?.Log?.Info($" using existing bearer token."); + client.SetBearerToken(oidc.token); + } + else + { + if (oidc.email != "") + { + runtimeOptions?.Log?.Info($" using existing email token."); + client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); + } } } - // go to error - return null; - } + // BEGIN Workaround behind some proxies + // Stream is sent twice, if proxy-authorization header is not set + string proxyFile = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "/proxy.dat"; + string username = ""; + string password = ""; + if (System.IO.File.Exists(proxyFile)) + { + using (StreamReader sr = new StreamReader(proxyFile)) + { + // ReSharper disable MethodHasAsyncOverload + sr.ReadLine(); + username = sr.ReadLine(); + password = sr.ReadLine(); + // ReSharper enable MethodHasAsyncOverload + } + } + if (username != "" && password != "") + { + var authToken = Encoding.ASCII.GetBytes(username + ":" + password); + client.DefaultRequestHeaders.ProxyAuthorization = new AuthenticationHeaderValue("Basic", + Convert.ToBase64String(authToken)); + } + // END Workaround behind some proxies - //public static string CombineUri (string uri1, string uri2) - //{ - // var res = "" + uri1; - // if (uri2?.HasContent() == true) - // { - // if (!res.EndsWith("/")) - // res += "/"; - // res += uri2; - // } - // return res; - //} - - public static Uri CombineUri(Uri baseUri, string relativeUri) - { - if (baseUri == null || relativeUri?.HasContent() != true) - return null; + // make base64 + //var ba = ms.ToArray(); + //var base64 = Convert.ToBase64String(ba); - if (Uri.TryCreate(baseUri, relativeUri, out var res)) - return res; + // customised HttpContent to track progress + // var data = new ProgressableStreamContent(Encoding.UTF8.GetBytes(base64), runtimeOptions); + var data = new ProgressableStreamContent(ms.ToArray(), runtimeOptions); - return null; - } + // get response? + using (var response = await client.PutAsync(requestPath, data)) + { + if (response.IsSuccessStatusCode) + await response.Content.ReadAsStringAsync(); - public static Uri BuildUriForSubmodel(Uri baseUri, Aas.IReference submodelRef) - { - // access - if (baseUri == null || submodelRef?.IsValid() != true - || submodelRef.Count() != 1 || submodelRef.Keys[0].Type != KeyTypes.Submodel) - return null; + if (!response.IsSuccessStatusCode) + { + runtimeOptions?.Log?.Error("HttpPutFromMemoryStream Server gave: Operation not allowed!"); + throw new PackageContainerException($"Server operation not allowed!"); + } - // try combine - var smidenc = AdminShellUtil.Base64Encode(submodelRef.Keys[0].Value); - return CombineUri(baseUri, $"submodels/{smidenc}"); + // ok! + return true; + } } } diff --git a/src/AasxPackageLogic/Resources/PackageContainerFakeAnswers.json b/src/AasxPackageLogic/Resources/PackageContainerFakeAnswers.json index 0d0e1f049..626b5c09d 100644 --- a/src/AasxPackageLogic/Resources/PackageContainerFakeAnswers.json +++ b/src/AasxPackageLogic/Resources/PackageContainerFakeAnswers.json @@ -13,7 +13,12 @@ // Single Submodel { "Request": "https://cloudrepo.aas-voyager.com/submodels/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ==", - "Response": "ICB7CiAgICAiaWRTaG9ydCI6ICJOYW1lcGxhdGUiLAogICAgImRlc2NyaXB0aW9uIjogWwogICAgICB7CiAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAidGV4dCI6ICJDb250YWlucyB0aGUgbmFtZXBsYXRlIGluZm9ybWF0aW9uIGF0dGFjaGVkIHRvIHRoZSBwcm9kdWN0IgogICAgICB9CiAgICBdLAogICAgImFkbWluaXN0cmF0aW9uIjogewogICAgICAidmVyc2lvbiI6ICIyIiwKICAgICAgInJldmlzaW9uIjogIjAiCiAgICB9LAogICAgImlkIjogImh0dHBzOi8vc21hcnQuZmVzdG8uY29tL3NtLzAwMS8wNjBmZjY0Zi05ZmQyLTQyMmQtODFjZS1iMTdlNDlmMDA3YzUvIiwKICAgICJraW5kIjogIkluc3RhbmNlIiwKICAgICJzZW1hbnRpY0lkIjogewogICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICJrZXlzIjogWwogICAgICAgIHsKICAgICAgICAgICJ0eXBlIjogIlN1Ym1vZGVsIiwKICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgInN1Ym1vZGVsRWxlbWVudHMiOiBbCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJVUklPZlRoZVByb2R1Y3QiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFZODExIzAwMSIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICJ2YWx1ZSI6ICIiLAogICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJOYW1lIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzY3NyMwMDIiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgInRleHQiOiAiRkVTVE8gU0UgXHUwMDI2IENvLiBLRyIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIkZFU1RPIFNFIFx1MDAyNiBDby4gS0ciCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIk1hbnVmYWN0dXJlclByb2R1Y3REZXNpZ25hdGlvbiIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVczMzgjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICJ0ZXh0IjogIkVpbnppZ2FydGlnIGZsZXhpYmVsIGltIEFuc2NobHVzczogZGVyIERydWNrc2Vuc29yIFNQQVUuIE9iIERydWNrbWVzc3VuZywgRHJ1Y2tcdTAwRkNiZXJ3YWNodW5nIG9kZXIgRHJ1Y2thYmZyYWdlIFx1MjAxMyBTaWUgaGFiZW4gYWxsZSBEcnVja3dlcnRlIGltbWVyIGF1ZiBlaW5lbiBCbGljayB1bnRlciBLb250cm9sbGUuIEltIElPLUxpbmstTW9kZSBzaW5kIEZlcm53YXJ0dW5nIHVuZCAtcGFyYW1ldHJpZXJ1bmcgc293aWUgZWluZSBlaW5mYWNoZSBTZW5zb3JlbnJlcGxpemllcnVuZyBtXHUwMEY2Z2xpY2guIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgInRleHQiOiAiVGhlIHByZXNzdXJlIHNlbnNvciBTUEFVIGhhcyBhIHVuaXF1ZWx5IGZsZXhpYmxlIGNvbm5lY3Rpb24uIFdoZXRoZXIgZm9yIHByZXNzdXJlIG1lYXN1cmluZywgcHJlc3N1cmUgbW9uaXRvcmluZyBvciBwcmVzc3VyZSBzZW5zaW5nLCBhbGwgcHJlc3N1cmUgdmFsdWVzIGFyZSBhbHdheXMgdW5kZXIgY29udHJvbCBhdCBhIGdsYW5jZS4gUmVtb3RlIG1haW50ZW5hbmNlIGFuZCBwYXJhbWV0ZXJpc2F0aW9uIGFzIHdlbGwgYXMgc2ltcGxlIHNlbnNvciByZXBsaWNhdGlvbiBhcmUgcG9zc2libGUgaW4gSU8tTGlua1x1MDBBRSBtb2RlLiIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiTWFudWZhY3R1cmVyUHJvZHVjdFJvb3QiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFVNzMyIzAwMSIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAidGV4dCI6ICJEcnVja3NlbnNvciIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIlByZXNzdXJlIHNlbnNvciIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiTWFudWZhY3R1cmVyUHJvZHVjdEZhbWlseSIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVU3MzEjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICJ0ZXh0IjogIlNQQVUiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAidGV4dCI6ICJTUEFVIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJQcm9kdWN0VHlwZSIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8wNTcjMDAyIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICJ0ZXh0IjogIlNQQVUtUDEwUi1ULVIxOE0tTC1QTkxLLVBOVkJBLU04RCIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIlNQQVUtUDEwUi1ULVIxOE0tTC1QTkxLLVBOVkJBLU04RCIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiT3JkZXJDb2RlT2ZNYW51ZmFjdHVyZXIiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMjI3IzAwMiIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAidGV4dCI6ICJTUEFVLVAxMFItVC1SMThNLUwtUE5MSy1QTlZCQS1NOEQiCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIlByb2R1Y3RBcnRpY2xlTnVtYmVyT2ZNYW51ZmFjdHVyZXIiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPNjc2IzAwMyIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAidGV4dCI6ICI4MDAxMjAzIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJTZXJpYWxOdW1iZXIiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFNNTU2IzAwMiIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICJ2YWx1ZSI6ICIiLAogICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJZZWFyT2ZDb25zdHJ1Y3Rpb24iLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFQOTA2IzAwMSIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICJ2YWx1ZSI6ICIyMDIzIiwKICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiQ291bnRyeU9mT3JpZ2luIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzI1OSMwMDQiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAidmFsdWUiOiAiREUiLAogICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJDb21wYW55TG9nbyIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZS9Db21wYW55TG9nbyAiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6ICIvYWFzeC9OYW1lcGxhdGUvRmVzdG9Mb2dvLnBuZyIsCiAgICAgICAgImNvbnRlbnRUeXBlIjogImltYWdlL3BuZy8iLAogICAgICAgICJtb2RlbFR5cGUiOiAiRmlsZSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIkNvbnRhY3RJbmZvcm1hdGlvbiIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMS8wL0NvbnRhY3RJbmZvcm1hdGlvbnMvQ29udGFjdEluZm9ybWF0aW9uIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIkRlcGFydG1lbnQiLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTI3IzAwMyIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAgICAgInRleHQiOiAiS29udGFrdCB6dSBGZXN0byIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJDb250YWN0IHRvIEZlc3RvIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJTdHJlZXQiLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTI4IzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAgICAgInRleHQiOiAiUnVpdGVyIFN0cmFcdTAwREZlIDgyIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIlJ1aXRlciBTdHJhXHUwMERGZSA4MiIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiWmlwY29kZSIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMjkjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICI3MzczNCIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICI3MzczNCIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiUE9Cb3giLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTMwIzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiWmlwQ29kZU9mUE9Cb3giLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTMxIzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiQ2l0eVRvd24iLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTMyIzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAgICAgInRleHQiOiAiRXNzbGluZ2VuIGFtIE5lY2thciIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJFc3NsaW5nZW4gYW0gTmVja2FyIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJTdGF0ZUNvdW50eSIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMzMjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJCYWRlbi1XdWVydHRlbWJlcmciCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAgICAgInRleHQiOiAiQmFkZW4tV1x1MDBGQ3J0dGVtYmVyZyIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiTmF0aW9uYWxDb2RlIiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzEzNCMwMDIiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIkRFIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIkRFIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJBZGRyZXNzT2ZBZGRpdGlvbmFsTGluayIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVEzMjYjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAidmFsdWUiOiAid3d3LmZlc3RvLmNvbS9jb250YWN0IiwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIlBob25lIiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8xLzAvQ29udGFjdEluZm9ybWF0aW9ucy9Db250YWN0SW5mb3JtYXRpb24vUGhvbmUiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiVGVsZXBob25lTnVtYmVyIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzEzNiMwMDIiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgICAgICAgICAidGV4dCI6ICJcdTAwMkI0OSAoMCkgNzExIDM0NyAwIgogICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICAgICAidGV4dCI6ICJcdTAwMkI0OSAoMCkgNzExIDM0NyAwIgogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJUeXBlT2ZUZWxlcGhvbmUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTM3IzAwMyIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAib2ZmaWNlIiwKICAgICAgICAgICAgICAgICJ2YWx1ZUlkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzA3LUFBUzc1NCMwMDEiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIkZheCIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVE4MzQjMDA1IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIkZheE51bWJlciIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xOTUjMDAyIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAgICAgInRleHQiOiAiXHUwMDJCNDkgKDApIDcxMSAzNDcgMjE0NCIKICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAgICAgInRleHQiOiAiXHUwMDJCNDkgKDApIDcxMSAzNDcgMjE0NCIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiVHlwZU9mRmF4TnVtYmVyIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzE5NiMwMDMiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIm9mZmljZSIsCiAgICAgICAgICAgICAgICAidmFsdWVJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwNy1BQVM3NTQjMDAxIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5ncyIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BR1o2NzMjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmcwMSIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BSEQyMDYjMDAxIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdOYW1lIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ05hbWUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIm5hY2ggRVUtRU1WLVJpY2h0bGluaWUiLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdGaWxlIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ0ZpbGUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlIjogIi9hYXN4L05hbWVwbGF0ZS9DRV9NYXJraW5nXzIwMTYucG5nIiwKICAgICAgICAgICAgICAgICJjb250ZW50VHlwZSI6ICJhcHBsaWNhdGlvbi9wbmciLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJGaWxlIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0FkZGl0aW9uYWxUZXh0IiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ0FkZGl0aW9uYWxUZXh0IgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJhY2NvcmRpbmcgdG8gRU1DIGRpcmVjdGl2ZSAyMDE0LzMwL0VVOyBpbmNsdWRlcyBFVS1Sb0hTIiwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nMDIiLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDEtQUhEMjA2IzAwMSIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nTmFtZSIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZS9NYXJraW5ncy9NYXJraW5nL01hcmtpbmdOYW1lIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJuYWNoIEVVLVJvSFMtUmljaHRsaW5pZSIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0ZpbGUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nRmlsZSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2Fhc3gvTmFtZXBsYXRlL2NoaW5hLVJvSFMtdHJhbnNwYXJlbnQucG5nIiwKICAgICAgICAgICAgICAgICJjb250ZW50VHlwZSI6ICJhcHBsaWNhdGlvbi9wbmciLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJGaWxlIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0FkZGl0aW9uYWxUZXh0IiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ0FkZGl0aW9uYWxUZXh0IgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJmb3IgQ2hpbmEiLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmcwMyIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BSEQyMDYjMDAxIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdOYW1lIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ05hbWUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIlJDTSBNYXJrIiwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nRmlsZSIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZS9NYXJraW5ncy9NYXJraW5nL01hcmtpbmdGaWxlIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIvYWFzeC9OYW1lcGxhdGUvUkNNLnBuZyIsCiAgICAgICAgICAgICAgICAiY29udGVudFR5cGUiOiAiYXBwbGljYXRpb24vcG5nIiwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiRmlsZSIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmcwNCIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BSEQyMDYjMDAxIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdOYW1lIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ05hbWUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogImMgVUwgdXMgLSBMaXN0ZWQgKE9MKSIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0ZpbGUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nRmlsZSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2Fhc3gvTmFtZXBsYXRlL2MtdWwtdXMtdHJhbnNwYXJlbnQucG5nIiwKICAgICAgICAgICAgICAgICJjb250ZW50VHlwZSI6ICJhcHBsaWNhdGlvbi9wbmciLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJGaWxlIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgogICAgICB9CiAgICBdLAogICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbCIKICB9" + "Response": "ICB7CiAgICAiaWRTaG9ydCI6ICJOYW1lcGxhdGUiLAogICAgImRlc2NyaXB0aW9uIjogWwogICAgICB7CiAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAidGV4dCI6ICJDb250YWlucyB0aGUgbmFtZXBsYXRlIGluZm9ybWF0aW9uIGF0dGFjaGVkIHRvIHRoZSBwcm9kdWN0IgogICAgICB9CiAgICBdLAogICAgImFkbWluaXN0cmF0aW9uIjogewogICAgICAidmVyc2lvbiI6ICIyIiwKICAgICAgInJldmlzaW9uIjogIjAiCiAgICB9LAogICAgImlkIjogImh0dHBzOi8vZXhhbXBsZS5jb20vaWRzL3NtLzIwMTVfNjAyMF8zMDEyXzA1ODUiLAogICAgImtpbmQiOiAiSW5zdGFuY2UiLAogICAgInNlbWFudGljSWQiOiB7CiAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAiU3VibW9kZWwiLAogICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZSIKICAgICAgICB9CiAgICAgIF0KICAgIH0sCiAgICAic3VibW9kZWxFbGVtZW50cyI6IFsKICAgICAgewogICAgICAgICJpZFNob3J0IjogIlVSSU9mVGhlUHJvZHVjdCIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVk4MTEjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgInZhbHVlIjogIiIsCiAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIk1hbnVmYWN0dXJlck5hbWUiLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPNjc3IzAwMiIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAidGV4dCI6ICJGRVNUTyBTRSBcdTAwMjYgQ28uIEtHIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgInRleHQiOiAiRkVTVE8gU0UgXHUwMDI2IENvLiBLRyIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiTWFudWZhY3R1cmVyUHJvZHVjdERlc2lnbmF0aW9uIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBVzMzOCMwMDEiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgInRleHQiOiAiRWluemlnYXJ0aWcgZmxleGliZWwgaW0gQW5zY2hsdXNzOiBkZXIgRHJ1Y2tzZW5zb3IgU1BBVS4gT2IgRHJ1Y2ttZXNzdW5nLCBEcnVja1x1MDBGQ2JlcndhY2h1bmcgb2RlciBEcnVja2FiZnJhZ2UgXHUyMDEzIFNpZSBoYWJlbiBhbGxlIERydWNrd2VydGUgaW1tZXIgYXVmIGVpbmVuIEJsaWNrIHVudGVyIEtvbnRyb2xsZS4gSW0gSU8tTGluay1Nb2RlIHNpbmQgRmVybndhcnR1bmcgdW5kIC1wYXJhbWV0cmllcnVuZyBzb3dpZSBlaW5lIGVpbmZhY2hlIFNlbnNvcmVucmVwbGl6aWVydW5nIG1cdTAwRjZnbGljaC4iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAidGV4dCI6ICJUaGUgcHJlc3N1cmUgc2Vuc29yIFNQQVUgaGFzIGEgdW5pcXVlbHkgZmxleGlibGUgY29ubmVjdGlvbi4gV2hldGhlciBmb3IgcHJlc3N1cmUgbWVhc3VyaW5nLCBwcmVzc3VyZSBtb25pdG9yaW5nIG9yIHByZXNzdXJlIHNlbnNpbmcsIGFsbCBwcmVzc3VyZSB2YWx1ZXMgYXJlIGFsd2F5cyB1bmRlciBjb250cm9sIGF0IGEgZ2xhbmNlLiBSZW1vdGUgbWFpbnRlbmFuY2UgYW5kIHBhcmFtZXRlcmlzYXRpb24gYXMgd2VsbCBhcyBzaW1wbGUgc2Vuc29yIHJlcGxpY2F0aW9uIGFyZSBwb3NzaWJsZSBpbiBJTy1MaW5rXHUwMEFFIG1vZGUuIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJQcm9kdWN0Um9vdCIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVU3MzIjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICJ0ZXh0IjogIkRydWNrc2Vuc29yIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgInRleHQiOiAiUHJlc3N1cmUgc2Vuc29yIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJQcm9kdWN0RmFtaWx5IiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBVTczMSMwMDEiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgInRleHQiOiAiU1BBVSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIlNQQVUiCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIk1hbnVmYWN0dXJlclByb2R1Y3RUeXBlIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzA1NyMwMDIiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgInRleHQiOiAiU1BBVS1QMTBSLVQtUjE4TS1MLVBOTEstUE5WQkEtTThEIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgInRleHQiOiAiU1BBVS1QMTBSLVQtUjE4TS1MLVBOTEstUE5WQkEtTThEIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJPcmRlckNvZGVPZk1hbnVmYWN0dXJlciIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8yMjcjMDAyIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIlNQQVUtUDEwUi1ULVIxOE0tTC1QTkxLLVBOVkJBLU04RCIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiUHJvZHVjdEFydGljbGVOdW1iZXJPZk1hbnVmYWN0dXJlciIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU82NzYjMDAzIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICJ0ZXh0IjogIjgwMDEyMDMiCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIlNlcmlhbE51bWJlciIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU01NTYjMDAyIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgInZhbHVlIjogIiIsCiAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIlllYXJPZkNvbnN0cnVjdGlvbiIsCiAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVA5MDYjMDAxIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfSwKICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgInZhbHVlIjogIjIwMjMiLAogICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWRTaG9ydCI6ICJDb3VudHJ5T2ZPcmlnaW4iLAogICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMjU5IzAwNCIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICJ2YWx1ZSI6ICJERSIsCiAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIkNvbXBhbnlMb2dvIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL0NvbXBhbnlMb2dvICIKICAgICAgICAgICAgfQogICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgInZhbHVlIjogIi9hYXN4L05hbWVwbGF0ZS9GZXN0b0xvZ28ucG5nIiwKICAgICAgICAiY29udGVudFR5cGUiOiAiaW1hZ2UvcG5nLyIsCiAgICAgICAgIm1vZGVsVHlwZSI6ICJGaWxlIgogICAgICB9LAogICAgICB7CiAgICAgICAgImlkU2hvcnQiOiAiQ29udGFjdEluZm9ybWF0aW9uIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8xLzAvQ29udGFjdEluZm9ybWF0aW9ucy9Db250YWN0SW5mb3JtYXRpb24iCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiRGVwYXJ0bWVudCIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMjcjMDAzIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJLb250YWt0IHp1IEZlc3RvIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIkNvbnRhY3QgdG8gRmVzdG8iCiAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIlN0cmVldCIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMjgjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJSdWl0ZXIgU3RyYVx1MDBERmUgODIiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAgICAgInRleHQiOiAiUnVpdGVyIFN0cmFcdTAwREZlIDgyIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJaaXBjb2RlIiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzEyOSMwMDIiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIjczNzM0IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIjczNzM0IgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJQT0JveCIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMzAjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJaaXBDb2RlT2ZQT0JveCIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMzEjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJDaXR5VG93biIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMzIjMDAyIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJkZSIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJFc3NsaW5nZW4gYW0gTmVja2FyIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIkVzc2xpbmdlbiBhbSBOZWNrYXIiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIlN0YXRlQ291bnR5IiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzEzMyMwMDIiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgICAgICJ0ZXh0IjogIkJhZGVuLVd1ZXJ0dGVtYmVyZyIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJsYW5ndWFnZSI6ICJlbiIsCiAgICAgICAgICAgICAgICAidGV4dCI6ICJCYWRlbi1XXHUwMEZDcnR0ZW1iZXJnIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiaWRTaG9ydCI6ICJOYXRpb25hbENvZGUiLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTM0IzAwMiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAgICAgInRleHQiOiAiREUiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAgICAgInRleHQiOiAiREUiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIkFkZHJlc3NPZkFkZGl0aW9uYWxMaW5rIiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBUTMyNiMwMDIiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgICAgICJ2YWx1ZSI6ICJ3d3cuZmVzdG8uY29tL2NvbnRhY3QiLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiUGhvbmUiLAogICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzEvMC9Db250YWN0SW5mb3JtYXRpb25zL0NvbnRhY3RJbmZvcm1hdGlvbi9QaG9uZSIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJUZWxlcGhvbmVOdW1iZXIiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTM2IzAwMiIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZGUiLAogICAgICAgICAgICAgICAgICAgICJ0ZXh0IjogIlx1MDAyQjQ5ICgwKSA3MTEgMzQ3IDAiCiAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAibGFuZ3VhZ2UiOiAiZW4iLAogICAgICAgICAgICAgICAgICAgICJ0ZXh0IjogIlx1MDAyQjQ5ICgwKSA3MTEgMzQ3IDAiCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIlR5cGVPZlRlbGVwaG9uZSIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8xMzcjMDAzIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJvZmZpY2UiLAogICAgICAgICAgICAgICAgInZhbHVlSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDctQUFTNzU0IzAwMSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiRmF4IiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBUTgzNCMwMDUiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiRmF4TnVtYmVyIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzE5NSMwMDIiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImRlIiwKICAgICAgICAgICAgICAgICAgICAidGV4dCI6ICJcdTAwMkI0OSAoMCkgNzExIDM0NyAyMTQ0IgogICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImxhbmd1YWdlIjogImVuIiwKICAgICAgICAgICAgICAgICAgICAidGV4dCI6ICJcdTAwMkI0OSAoMCkgNzExIDM0NyAyMTQ0IgogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJUeXBlT2ZGYXhOdW1iZXIiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFPMTk2IzAwMyIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAib2ZmaWNlIiwKICAgICAgICAgICAgICAgICJ2YWx1ZUlkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzA3LUFBUzc1NCMwMDEiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgfSwKICAgICAgewogICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdzIiwKICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAxLUFHWjY3MyMwMDEiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9LAogICAgICAgICJ2YWx1ZSI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZzAxIiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAxLUFIRDIwNiMwMDEiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ05hbWUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nTmFtZSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAibmFjaCBFVS1FTVYtUmljaHRsaW5pZSIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ0ZpbGUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nRmlsZSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2Fhc3gvTmFtZXBsYXRlL0NFX01hcmtpbmdfMjAxNi5wbmciLAogICAgICAgICAgICAgICAgImNvbnRlbnRUeXBlIjogImFwcGxpY2F0aW9uL3BuZyIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIkZpbGUiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nQWRkaXRpb25hbFRleHQiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nQWRkaXRpb25hbFRleHQiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogImFjY29yZGluZyB0byBFTUMgZGlyZWN0aXZlIDIwMTQvMzAvRVU7IGluY2x1ZGVzIEVVLVJvSFMiLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmcwMiIsCiAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogIjAxNzMtMSMwMS1BSEQyMDYjMDAxIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZhbHVlIjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdOYW1lIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ05hbWUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIm5hY2ggRVUtUm9IUy1SaWNodGxpbmllIiwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nRmlsZSIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZS9NYXJraW5ncy9NYXJraW5nL01hcmtpbmdGaWxlIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIvYWFzeC9OYW1lcGxhdGUvY2hpbmEtUm9IUy10cmFuc3BhcmVudC5wbmciLAogICAgICAgICAgICAgICAgImNvbnRlbnRUeXBlIjogImFwcGxpY2F0aW9uL3BuZyIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIkZpbGUiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nQWRkaXRpb25hbFRleHQiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nQWRkaXRpb25hbFRleHQiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAogICAgICAgICAgICAgICAgInZhbHVlIjogImZvciBDaGluYSIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIlByb3BlcnR5IgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZzAzIiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAxLUFIRDIwNiMwMDEiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ05hbWUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nTmFtZSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiUkNNIE1hcmsiLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJpZFNob3J0IjogIk1hcmtpbmdGaWxlIiwKICAgICAgICAgICAgICAgICJzZW1hbnRpY0lkIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby96dmVpL25hbWVwbGF0ZS8yLzAvTmFtZXBsYXRlL01hcmtpbmdzL01hcmtpbmcvTWFya2luZ0ZpbGUiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInZhbHVlIjogIi9hYXN4L05hbWVwbGF0ZS9SQ00ucG5nIiwKICAgICAgICAgICAgICAgICJjb250ZW50VHlwZSI6ICJhcHBsaWNhdGlvbi9wbmciLAogICAgICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJGaWxlIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZzA0IiwKICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICJrZXlzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiMDE3My0xIzAxLUFIRDIwNiMwMDEiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgXQogICAgICAgICAgICB9LAogICAgICAgICAgICAidmFsdWUiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkU2hvcnQiOiAiTWFya2luZ05hbWUiLAogICAgICAgICAgICAgICAgInNlbWFudGljSWQiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgImtleXMiOiBbCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL3p2ZWkvbmFtZXBsYXRlLzIvMC9OYW1lcGxhdGUvTWFya2luZ3MvTWFya2luZy9NYXJraW5nTmFtZSIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiYyBVTCB1cyAtIExpc3RlZCAoT0wpIiwKICAgICAgICAgICAgICAgICJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWRTaG9ydCI6ICJNYXJraW5nRmlsZSIsCiAgICAgICAgICAgICAgICAic2VtYW50aWNJZCI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAia2V5cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAogICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8venZlaS9uYW1lcGxhdGUvMi8wL05hbWVwbGF0ZS9NYXJraW5ncy9NYXJraW5nL01hcmtpbmdGaWxlIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICIvYWFzeC9OYW1lcGxhdGUvYy11bC11cy10cmFuc3BhcmVudC5wbmciLAogICAgICAgICAgICAgICAgImNvbnRlbnRUeXBlIjogImFwcGxpY2F0aW9uL3BuZyIsCiAgICAgICAgICAgICAgICAibW9kZWxUeXBlIjogIkZpbGUiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgICAgIH0KICAgIF0sCiAgICAibW9kZWxUeXBlIjogIlN1Ym1vZGVsIgogIH0=" + }, + // Single Submodel + { + "Request": "https://cloudrepo.aas-voyager.com/submodels/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vNTQzMV8yMTgxXzMwMTJfMzkyOQ==", + "Response": "ewoiaWRTaG9ydCI6ICJUZWNobmljYWxEYXRhIiwKImRlc2NyaXB0aW9uIjogWwogIHsKCSJsYW5ndWFnZSI6ICJlbiIsCgkidGV4dCI6ICJTdWJtb2RlbCBjb250YWluaW5nIHRlY2hpY2FsIGRhdGEgb2YgdGhlIGFzc2V0IGFuZCBhc3NvY2lhdGVkIHByb2R1Y3QgY2xhc3NpZmljYXRvbnMuIgogIH0sCiAgewoJImxhbmd1YWdlIjogImRlIiwKCSJ0ZXh0IjogIlRlaWxtb2RlbGwsIGRhcyBkaWUgdGVjaG5pc2NoZW4gRGF0ZW4gZGVyIEFubGFnZSB1bmQgZGllIHp1Z2VoXHUwMEY2cmlnZW4gUHJvZHVrdGtsYXNzaWZpemllcnVuZ2VuIGVudGhcdTAwRTRsdC4iCiAgfQpdLAoiYWRtaW5pc3RyYXRpb24iOiB7CiAgInZlcnNpb24iOiAiMSIsCiAgInJldmlzaW9uIjogIjIiCn0sCiJpZCI6ICJodHRwczovL2V4YW1wbGUuY29tL2lkcy9zbS81NDMxXzIxODFfMzAxMl8zOTI5IiwKImtpbmQiOiAiSW5zdGFuY2UiLAoic2VtYW50aWNJZCI6IHsKICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCiAgImtleXMiOiBbCgl7CgkgICJ0eXBlIjogIlN1Ym1vZGVsIiwKCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL1N1Ym1vZGVsLzEvMiIKCX0KICBdCn0sCiJzdWJtb2RlbEVsZW1lbnRzIjogWwogIHsKCSJpZFNob3J0IjogIkdlbmVyYWxJbmZvcm1hdGlvbiIsCgkic2VtYW50aWNJZCI6IHsKCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJICAia2V5cyI6IFsKCQl7CgkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL0dlbmVyYWxJbmZvcm1hdGlvbi8xLzEiCgkJfQoJICBdCgl9LAoJInZhbHVlIjogWwoJICB7CgkJImlkU2hvcnQiOiAiTWFudWZhY3R1cmVyTmFtZSIsCgkJInNlbWFudGljSWQiOiB7CgkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJICAia2V5cyI6IFsKCQkJewoJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU82NzcjMDAyIgoJCQl9CgkJICBdCgkJfSwKCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJInZhbHVlIjogIkZlc3RvIFNFIFx1MDAyNiBDby4gS0ciLAoJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJMb2dvIiwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvTWFudWZhY3R1cmVyTG9nby8xLzEiCgkJCX0KCQkgIF0KCQl9LAoJCSJ2YWx1ZSI6ICIvYWFzeC9UZWNobmljYWxEYXRhL0Zlc3RvTG9nby5wbmciLAoJCSJjb250ZW50VHlwZSI6ICJpbWFnZS9wbmcvIiwKCQkibW9kZWxUeXBlIjogIkZpbGUiCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJQcm9kdWN0RGVzaWduYXRpb24iLAoJCSJzZW1hbnRpY0lkIjogewoJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCSAgImtleXMiOiBbCgkJCXsKCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFXMzM4IzAwMSAiCgkJCX0KCQkgIF0KCQl9LAoJCSJ2YWx1ZSI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiRWluemlnYXJ0aWcgZmxleGliZWwgaW0gQW5zY2hsdXNzOiBkZXIgRHJ1Y2tzZW5zb3IgU1BBVS4gT2IgRHJ1Y2ttZXNzdW5nLCBEcnVja1x1MDBGQ2JlcndhY2h1bmcgb2RlciBEcnVja2FiZnJhZ2UgXHUyMDEzIFNpZSBoYWJlbiBhbGxlIERydWNrd2VydGUgaW1tZXIgYXVmIGVpbmVuIEJsaWNrIHVudGVyIEtvbnRyb2xsZS4gSW0gSU8tTGluay1Nb2RlIHNpbmQgRmVybndhcnR1bmcgdW5kIC1wYXJhbWV0cmllcnVuZyBzb3dpZSBlaW5lIGVpbmZhY2hlIFNlbnNvcmVucmVwbGl6aWVydW5nIG1cdTAwRjZnbGljaC4iCgkJICB9LAoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkidGV4dCI6ICJUaGUgcHJlc3N1cmUgc2Vuc29yIFNQQVUgaGFzIGEgdW5pcXVlbHkgZmxleGlibGUgY29ubmVjdGlvbi4gV2hldGhlciBmb3IgcHJlc3N1cmUgbWVhc3VyaW5nLCBwcmVzc3VyZSBtb25pdG9yaW5nIG9yIHByZXNzdXJlIHNlbnNpbmcsIGFsbCBwcmVzc3VyZSB2YWx1ZXMgYXJlIGFsd2F5cyB1bmRlciBjb250cm9sIGF0IGEgZ2xhbmNlLiBSZW1vdGUgbWFpbnRlbmFuY2UgYW5kIHBhcmFtZXRlcmlzYXRpb24gYXMgd2VsbCBhcyBzaW1wbGUgc2Vuc29yIHJlcGxpY2F0aW9uIGFyZSBwb3NzaWJsZSBpbiBJTy1MaW5rXHUwMEFFIG1vZGUuIgoJCSAgfQoJCV0sCgkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJNYW51ZmFjdHVyZXJBcnRpY2xlTnVtYmVyIiwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzY3NiMwMDMiCgkJCX0KCQkgIF0KCQl9LAoJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkidmFsdWUiOiAiODAwMTIwMyIsCgkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCSAgfSwKCSAgewoJCSJpZFNob3J0IjogIk1hbnVmYWN0dXJlck9yZGVyQ29kZSIsCgkJInNlbWFudGljSWQiOiB7CgkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJICAia2V5cyI6IFsKCQkJewoJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU8yMjcjMDAyIgoJCQl9CgkJICBdCgkJfSwKCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJInZhbHVlIjogIlNQQVUtUDEwUi1ULVIxOE0tTC1QTkxLLVBOVkJBLU04RCIsCgkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCSAgfSwKCSAgewoJCSJpZFNob3J0IjogIlByb2R1Y3RJbWFnZTAxIiwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvUHJvZHVjdEltYWdlLzEvMSIKCQkJfQoJCSAgXQoJCX0sCgkJInZhbHVlIjogIi9hYXN4L1RlY2huaWNhbERhdGEvRDE1MDAwMTAwMTE3MzUwXzEwNTZ4MTAyNC5qcGciLAoJCSJjb250ZW50VHlwZSI6ICJpbWFnZS9qcGVnIiwKCQkibW9kZWxUeXBlIjogIkZpbGUiCgkgIH0KCV0sCgkibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgfSwKICB7CgkiaWRTaG9ydCI6ICJQcm9kdWN0Q2xhc3NpZmljYXRpb25zIiwKCSJzZW1hbnRpY0lkIjogewoJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkgICJrZXlzIjogWwoJCXsKCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvUHJvZHVjdENsYXNzaWZpY2F0aW9ucy8xLzEiCgkJfQoJICBdCgl9LAoJInZhbHVlIjogWwoJICB7CgkJImlkU2hvcnQiOiAiUHJvZHVjdENsYXNzaWZpY2F0aW9uSXRlbTAxIiwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvUHJvZHVjdENsYXNzaWZpY2F0aW9uSXRlbS8xLzEiCgkJCX0KCQkgIF0KCQl9LAoJCSJ2YWx1ZSI6IFsKCQkgIHsKCQkJImlkU2hvcnQiOiAiUHJvZHVjdENsYXNzaWZpY2F0aW9uU3lzdGVtIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvUHJvZHVjdENsYXNzaWZpY2F0aW9uU3lzdGVtLzEvMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIkVDTEFTUyIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJDbGFzc2lmaWNhdGlvblN5c3RlbVZlcnNpb24iLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICJodHRwczovL2FkbWluLXNoZWxsLmlvL1pWRUkvVGVjaG5pY2FsRGF0YS9DbGFzc2lmaWNhdGlvblN5c3RlbVZlcnNpb24vMS8xIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAoJCQkidmFsdWUiOiAiMTMuMCAoQkFTSUMpIiwKCQkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIlByb2R1Y3RDbGFzc0lkIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvUHJvZHVjdENsYXNzSWQvMS8xIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAoJCQkidmFsdWUiOiAiMjctMjctNDktMDEiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfQoJCV0sCgkJIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgoJICB9CgldLAoJIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgogIH0sCiAgewoJImlkU2hvcnQiOiAiVGVjaG5pY2FsUHJvcGVydGllcyIsCgkic2VtYW50aWNJZCI6IHsKCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJICAia2V5cyI6IFsKCQl7CgkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL1RlY2huaWNhbFByb3BlcnRpZXMvMS8xIgoJCX0KCSAgXQoJfSwKCSJ2YWx1ZSI6IFsKCSAgewoJCSJpZFNob3J0IjogIkdlbmVyYWxUZWNobmljYWxEYXRhIiwKCQkiZGlzcGxheU5hbWUiOiBbCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCSJ0ZXh0IjogIkFsbGdlbWVpbmUgVGVjaG5pc2NoZSBEYXRlbiIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIkdlbmVyYWwgdGVjaG5pY2FsIGRhdGEiCgkJICB9CgkJXSwKCQkiZGVzY3JpcHRpb24iOiBbCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCSJ0ZXh0IjogIkFsbGdlbWVpbmUgVGVjaG5pc2NoZSBEYXRlbiIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIkdlbmVyYWwgdGVjaG5pY2FsIGRhdGEiCgkJICB9CgkJXSwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvTWFpblNlY3Rpb24vMS8xIgoJCQl9CgkJICBdCgkJfSwKCQkidmFsdWUiOiBbCgkJICB7CgkJCSJpZFNob3J0IjogIkNVTHVzIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBUjg2OCMwMDQiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOmJvb2xlYW4iLAoJCQkidmFsdWUiOiAiVFJVRSIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJUeXBlT2ZBcHByb3ZhbCIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQU04MTIjMDAzIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJSQ00gTWFyayIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCQkidGV4dCI6ICJSQ00gdHJhZGVtYXJrIgoJCQkgIH0KCQkJXSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJDZXJ0aWZpY2F0ZWFwcHJvdmFsIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUJBQjM5MiMwMTYiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWUiOiBbCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImRlIiwKCQkJCSJ0ZXh0IjogIm5hY2ggRVUtRU1WLVJpY2h0bGluaWVcbm5hY2ggRVUtUm9IUy1SaWNodGxpbmllIgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIlRvIEVVIEVNQyBEaXJlY3RpdmVcbkluIGFjY29yZGFuY2Ugd2l0aCBFVSBSb0hTIERpcmVjdGl2ZSIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiQ29ubmVjdGlvblR5cGUiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUJDMjY0IzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZSI6IFsKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkJInRleHQiOiAiU3RlY2tlciIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCQkidGV4dCI6ICJQbHVncyIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiVGhyZWFkU2l6ZUNvbm5lY3RvciIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQkMyNjUjMDAxIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJNOHgxIEEtY29kaWVydCBuYWNoIEVOIDYxMDc2LTItMTA0IgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIk04eDEsIEEtY29kZWQsIHRvIEVOXHUwMEEwNjEwNzYtMi0xMDQiCgkJCSAgfQoJCQldLAoJCQkibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIkNvZGluZyIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQkMzMzgjMDAxIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJNOHgxIEEtY29kaWVydCBuYWNoIEVOIDYxMDc2LTItMTA0IgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIk04eDEsIEEtY29kZWQsIHRvIEVOXHUwMEEwNjEwNzYtMi0xMDQiCgkJCSAgfQoJCQldLAoJCQkibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIlR5cGVPZkNvbm5lY3RvciIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQkMyNjYjMDAxIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJNOHgxIEEtY29kaWVydCBuYWNoIEVOIDYxMDc2LTItMTA0IgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIk04eDEsIEEtY29kZWQsIHRvIEVOXHUwMEEwNjEwNzYtMi0xMDQiCgkJCSAgfQoJCQldLAoJCQkibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIk51bWJlck9mUG9sZXMiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFQNjM2IzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIjQiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiTW91bnRpbmdPcmllbnRhdGlvbiIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVEyMTEjMDA1IgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJiZWxpZWJpZyIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCQkidGV4dCI6ICJvcHRpb25hbCIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiUm9Ic0NvbmZvcm1pdHkiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFaOTcwIzAwMiIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZSI6IFsKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkJInRleHQiOiAiUm9IUyBrb25mb3JtIgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIlJvSFMtY29tcGxpYW50IgoJCQkgIH0KCQkJXSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJNVFRGIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBUjg2MSMwMDMiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIxMjIuOCIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9CgkJXSwKCQkibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJJbW1pc3Npb25fRW1pc3Npb24iLAoJCSJkaXNwbGF5TmFtZSI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiSW1taXNzaW9uL2VtaXNzaW9uIgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiSW1taXNzaW9uL2VtaXNzaW9uIgoJCSAgfQoJCV0sCgkJImRlc2NyaXB0aW9uIjogWwoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkidGV4dCI6ICJJbW1pc3Npb24vZW1pc3Npb24iCgkJICB9LAoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkidGV4dCI6ICJJbW1pc3Npb24vZW1pc3Npb24iCgkJICB9CgkJXSwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvTWFpblNlY3Rpb24vMS8xIgoJCQl9CgkJICBdCgkJfSwKCQkidmFsdWUiOiBbCgkJICB7CgkJCSJpZFNob3J0IjogIkRlZ3JlZU9mUHJvdGVjdGlvbiIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1CQUc5NzUjMDEzIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJJUDY3XG5JUDY1IgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIklQNjdcbklQNjUiCgkJCSAgfQoJCQldLAoJCQkibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIk1pbkFtYmllbnRUZW1wZXJhdHVyZSIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQVo5NTIjMDAyIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAoJCQkidmFsdWUiOiAiMCIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJNYXhBbWJpZW50VGVtcGVyYXR1cmUiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQkFBMDM5IzAxMCIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIjUwIiwKCQkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCQkgIH0KCQldLAoJCSJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKCSAgfSwKCSAgewoJCSJpZFNob3J0IjogIklubGV0U2lnbmFsTWVhc3VyaW5nRWxlbWVuIiwKCQkiZGlzcGxheU5hbWUiOiBbCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCSJ0ZXh0IjogIkltbWlzc2lvbi9lbWlzc2lvbiIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIklubGV0IHNpZ25hbCwgbWVhc3VyaW5nIGVsZW1lbiIKCQkgIH0KCQldLAoJCSJkZXNjcmlwdGlvbiI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiRWluZ2FuZ3NzaWduYWwsIE1lc3NlbGVtZW50IgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiSW5sZXQgc2lnbmFsLCBtZWFzdXJpbmcgZWxlbWVuIgoJCSAgfQoJCV0sCgkJInNlbWFudGljSWQiOiB7CgkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJICAia2V5cyI6IFsKCQkJewoJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL01haW5TZWN0aW9uLzEvMSIKCQkJfQoJCSAgXQoJCX0sCgkJInZhbHVlIjogWwoJCSAgewoJCQkiaWRTaG9ydCI6ICJQcmVzc3VyZU1lYXN1cmVtZW50VmFyaWFibGVUeXBlIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBTzMxMyMwMDQiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWUiOiBbCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImRlIiwKCQkJCSJ0ZXh0IjogIlJlbGF0aXZkcnVjayIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCQkidGV4dCI6ICJSZWxhdGl2ZSBwcmVzc3VyZSIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiU3RhcnRWYWx1ZUZvclByZXNzdXJlTWVhc3VyaW5nUmFuZ2UiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUJEOTA1IzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIjAiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiRW5kVmFsdWVGb3JQcmVzc3VyZU1lYXN1cmluZ1JhbmdlIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFCRDkwNiMwMDEiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIxMCIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJPdmVybG9hZFByZXNzdXJlIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBWjg0MSMwMDEiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIxNSIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJPcGVyYXRpbmdNZWRpdW0iLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUJEOTA5IzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZSI6IFsKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkJInRleHQiOiAiRHJ1Y2tsdWZ0IG5hY2ggSVNPIDg1NzMtMToyMDEwIFs3OjQ6NF1cbkluZXJ0ZSBHYXNlIgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIkNvbXByZXNzZWQgYWlyIHRvIElTTyA4NTczLTE6MjAxMCBbNzo0OjRdXG5JbmVydCBnYXNlcyIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiTWluTWVkaXVtVGVtcGVyYXR1cmUiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFCOTg4IzAwNiIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIjAiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiTWF4TWVkaXVtVGVtcGVyYXR1cmUiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFCODY0IzAwNiIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIjUwIiwKCQkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCQkgIH0KCQldLAoJCSJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKCSAgfSwKCSAgewoJCSJpZFNob3J0IjogIlN3aXRjaGluZ091dHB1dCIsCgkJImRpc3BsYXlOYW1lIjogWwoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkidGV4dCI6ICJTY2hhbHRhdXNnYW5nIgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiU3dpdGNoaW5nIE91dHB1dCIKCQkgIH0KCQldLAoJCSJkZXNjcmlwdGlvbiI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiU2NoYWx0YXVzZ2FuZyIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIlN3aXRjaGluZyBPdXRwdXQiCgkJICB9CgkJXSwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvTWFpblNlY3Rpb24vMS8xIgoJCQl9CgkJICBdCgkJfSwKCQkidmFsdWUiOiBbCgkJICB7CgkJCSJpZFNob3J0IjogIlN3aXRjaGluZ091dHB1dFByZXNlbnQiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFDMDUwIzAwNyIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIlRSVUUiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiRGVzaWduT2ZDb250cm9sT3V0cHV0IiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUJBRDg5OCMwMDYiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWUiOiBbCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImRlIiwKCQkJCSJ0ZXh0IjogIjIgeCBQTlAgb2RlciAyIHggTlBOIHVtc2NoYWx0YmFyIgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIjIgeCBQTlAgb3IgMiB4IE5QTiwgc3dpdGNoYWJsZSIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiTWF4T3V0cHV0Q3VycmVudCIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1CQUM2NDAjMDA2IgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAoJCQkidmFsdWUiOiAiMTAwIiwKCQkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIlN3aXRjaEZ1bmN0aW9uIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBQzA5NyMwMDciCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWUiOiBbCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImRlIiwKCQkJCSJ0ZXh0IjogIkZyZWkgcHJvZ3JhbW1pZXJiYXIiCgkJCSAgfSwKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkJInRleHQiOiAiRnJlZWx5IHByb2dyYW1tYWJsZSIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiU3dpdGNoaW5nRWxlbWVudEZ1bmN0aW9uIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUJBRDg5OSMwMTAiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWUiOiBbCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImRlIiwKCQkJCSJ0ZXh0IjogIlx1MDBENmZmbmVyL1NjaGxpZVx1MDBERmVyIHVtc2NoYWx0YmFyIgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIk4vQyBvciBOL08gY29udGFjdCwgc3dpdGNoYWJsZSIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiTnVtYmVyT2ZTd2l0Y2hPdXRwdXRzIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBSTMzOSMwMDEiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIyIHggUE5QIG9kZXIgMiB4IE5QTiB1bXNjaGFsdGJhciIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9CgkJXSwKCQkibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJBbmFsb2d1ZU91dHB1dCIsCgkJImRpc3BsYXlOYW1lIjogWwoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkidGV4dCI6ICJBbmFsb2dhdXNnYW5nIgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiQW5hbG9ndWUgb3V0cHV0IgoJCSAgfQoJCV0sCgkJImRlc2NyaXB0aW9uIjogWwoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkidGV4dCI6ICJBbmFsb2dhdXNnYW5nIgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiQW5hbG9ndWUgb3V0cHV0IgoJCSAgfQoJCV0sCgkJInNlbWFudGljSWQiOiB7CgkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJICAia2V5cyI6IFsKCQkJewoJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL01haW5TZWN0aW9uLzEvMSIKCQkJfQoJCSAgXQoJCX0sCgkJInZhbHVlIjogWwoJCSAgewoJCQkiaWRTaG9ydCI6ICJBbmFsb2dPdXRwdXQiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQkFENzkyIzAxMyIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZSI6IFsKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkJInRleHQiOiAiMCAtIDEwIFZcbjEgLSA1IFZcbjQgLSAyMCBtQSIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCQkidGV4dCI6ICIwIC0gMTAgVlxuMSAtIDUgVlxuNCAtIDIwIG1BIgoJCQkgIH0KCQkJXSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJSaXNlVGltZSIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQkQ5MTcjMDAxIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAoJCQkidmFsdWUiOiAiMyIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJNaW5Mb2FkIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUJBQjIzOCMwMDciCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIxMCIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJNYXhMb2FkIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUJBQTA0MiMwMDkiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICI1MDAiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiTnVtYmVyT2ZBbmFsb2d1ZU91dHB1dHMiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFHNTQ1IzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIjEiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfQoJCV0sCgkJIm1vZGVsVHlwZSI6ICJTdWJtb2RlbEVsZW1lbnRDb2xsZWN0aW9uIgoJICB9LAoJICB7CgkJImlkU2hvcnQiOiAiT3V0cHV0QWRkaXRpb25hbERhdGEiLAoJCSJkaXNwbGF5TmFtZSI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiQXVzZ2FuZywgd2VpdGVyZSBEYXRlbiIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIk91dHB1dCwgYWRkaXRpb25hbCBkYXRhIgoJCSAgfQoJCV0sCgkJImRlc2NyaXB0aW9uIjogWwoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkidGV4dCI6ICJBdXNnYW5nLCB3ZWl0ZXJlIERhdGVuIgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiT3V0cHV0LCBhZGRpdGlvbmFsIGRhdGEiCgkJICB9CgkJXSwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvTWFpblNlY3Rpb24vMS8xIgoJCQl9CgkJICBdCgkJfSwKCQkidmFsdWUiOiBbCgkJICB7CgkJCSJpZFNob3J0IjogIlNob3J0Y2lyY3VpdFN0cmVuZ3RoQXZhaWxhYmxlIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFCRDg5NSMwMDEiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICJqYSIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9CgkJXSwKCQkibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJJT19MaW5rRGV2aWNlVG9JRUM2MTEzMV85IiwKCQkiZGlzcGxheU5hbWUiOiBbCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCSJ0ZXh0IjogIklPLUxpbmsgRGV2aWNlIG5hY2ggSUVDIDYxMTMxLTkiCgkJICB9LAoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkidGV4dCI6ICJJTy1MaW5rIGRldmljZSB0byBJRUMgNjExMzEtOSIKCQkgIH0KCQldLAoJCSJkZXNjcmlwdGlvbiI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiSU8tTGluayBEZXZpY2UgbmFjaCBJRUMgNjExMzEtOSIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIklPLUxpbmsgZGV2aWNlIHRvIElFQyA2MTEzMS05IgoJCSAgfQoJCV0sCgkJInNlbWFudGljSWQiOiB7CgkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJICAia2V5cyI6IFsKCQkJewoJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL01haW5TZWN0aW9uLzEvMSIKCQkJfQoJCSAgXQoJCX0sCgkJInZhbHVlIjogWwoJCSAgewoJCQkiaWRTaG9ydCI6ICJJbnRlcmZhY2VEZXNpZ24iLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFCNDAwIzAwOSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZSI6IFsKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZGVfREUiLAoJCQkJInRleHQiOiAiSU8tTGluayIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbl9HQiIsCgkJCQkidGV4dCI6ICJJTy1MaW5rXHUwMEFFIgoJCQkgIH0KCQkJXSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJJT0xpbmtSZXZpc2lvbnNJRCIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQkQ5MjIjMDAxIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJEZXZpY2UgViAxLjEiCgkJCSAgfSwKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkJInRleHQiOiAiRGV2aWNlIFYgMS4xIgoJCQkgIH0KCQkJXSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJJT0xpbmtEZXZpY2VQcm9maWxlcyIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQkQ5MjUjMDAxIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJTbWFydCBzZW5zb3IgcHJvZmlsZSIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCQkidGV4dCI6ICJTbWFydCBzZW5zb3IgcHJvZmlsZSIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiSU9MaW5rVHJhbnNtaXNzaW9uUmF0ZSIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQkQ5MjAjMDAxIgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJInZhbHVlIjogWwoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCQkidGV4dCI6ICJDT00yICgzOCw0IGtCYXVkKSIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCQkidGV4dCI6ICJDT00yICgzOC40IGtCYXVkKSIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiSU9MaW5rU0lPTW9kZVN1cHBvcnQiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUJEOTIzIzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIkphIiwKCQkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIklPTGlua1BvcnRUeXBlIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFCRDkyNCMwMDEiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWUiOiBbCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImRlIiwKCQkJCSJ0ZXh0IjogIkEiCgkJCSAgfSwKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkJInRleHQiOiAiQSIKCQkJICB9CgkJCV0sCgkJCSJtb2RlbFR5cGUiOiAiTXVsdGlMYW5ndWFnZVByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiSU9MaW5rUHJvY2Vzc0RhdGFPdXRwdXRMZW5ndGgiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUJEOTI3IzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIjAgQnl0ZSIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJJT0xpbmtQcm9jZXNzRGF0YUlucHV0TGVuZ3RoIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFCRDkyNiMwMDEiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIyIEJ5dGUiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiSU9MaW5rTWluQ3ljbGVUaW1lIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFCRDkyMSMwMDEiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIzIG1zIiwKCQkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCQkgIH0KCQldLAoJCSJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKCSAgfSwKCSAgewoJCSJpZFNob3J0IjogIkVsZWN0cm9uaWNzIiwKCQkiZGlzcGxheU5hbWUiOiBbCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCSJ0ZXh0IjogIkVsZWt0cm9uaWsiCgkJICB9LAoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkidGV4dCI6ICJFbGVjdHJvbmljcyIKCQkgIH0KCQldLAoJCSJkZXNjcmlwdGlvbiI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiRWxla3Ryb25payIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIkVsZWN0cm9uaWNzIgoJCSAgfQoJCV0sCgkJInNlbWFudGljSWQiOiB7CgkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJICAia2V5cyI6IFsKCQkJewoJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL01haW5TZWN0aW9uLzEvMSIKCQkJfQoJCSAgXQoJCX0sCgkJInZhbHVlIjogWwoJCSAgewoJCQkiaWRTaG9ydCI6ICJNaW5PcGVyYXRpbmdWb2x0YWdlV2l0aERDIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBQjk3MyMwMDgiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIyMCIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJNYXhPcGVyYXRpbmdWb2x0YWdlV2l0aERDIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBQjg0MCMwMDgiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIzMCIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJNYXhFbGVjdHJpY2l0eUNvbnN1bXB0aW9uIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBRjk1NCMwMDMiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWVUeXBlIjogInhzOnN0cmluZyIsCgkJCSJ2YWx1ZSI6ICIyNDAiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiUmV2ZXJzZVBvbGFyaXR5UHJvdGVjdGlvblByZXNlbnQiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFXOTc0IzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogImZcdTAwRkNyIGFsbGUgZWxla3RyaXNjaGVuIEFuc2NobFx1MDBGQ3NzZSIsCgkJCSJtb2RlbFR5cGUiOiAiUHJvcGVydHkiCgkJICB9CgkJXSwKCQkibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJEaXNwbGF5X09wZXJhdGlvbiIsCgkJImRpc3BsYXlOYW1lIjogWwoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkidGV4dCI6ICJBbnplaWdlL0JlZGllbnVuZyIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIkRpc3BsYXkvb3BlcmF0aW9uIgoJCSAgfQoJCV0sCgkJImRlc2NyaXB0aW9uIjogWwoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkidGV4dCI6ICJBbnplaWdlL0JlZGllbnVuZyIKCQkgIH0sCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCSJ0ZXh0IjogIkRpc3BsYXkvb3BlcmF0aW9uIgoJCSAgfQoJCV0sCgkJInNlbWFudGljSWQiOiB7CgkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJICAia2V5cyI6IFsKCQkJewoJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL01haW5TZWN0aW9uLzEvMSIKCQkJfQoJCSAgXQoJCX0sCgkJInZhbHVlIjogWwoJCSAgewoJCQkiaWRTaG9ydCI6ICJUeXBlT2ZJbmRpY2F0aW9uIiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUFBRzExMiMwMDciCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWUiOiBbCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImRlIiwKCQkJCSJ0ZXh0IjogIkxldWNodC1MQ0QiCgkJCSAgfSwKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkJInRleHQiOiAiSWxsdW1pbmF0ZWQgTENEIgoJCQkgIH0KCQkJXSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9CgkJXSwKCQkibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJQbmV1bWF0aWNDb25uZWN0aW9uIiwKCQkiZGlzcGxheU5hbWUiOiBbCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCSJ0ZXh0IjogIlBuZXVtYXRpc2NoZXIgQW5zY2hsdXNzIgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiUG5ldW1hdGljIGNvbm5lY3Rpb24iCgkJICB9CgkJXSwKCQkiZGVzY3JpcHRpb24iOiBbCgkJICB7CgkJCSJsYW5ndWFnZSI6ICJkZSIsCgkJCSJ0ZXh0IjogIlBuZXVtYXRpc2NoZXIgQW5zY2hsdXNzIgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiUG5ldW1hdGljIGNvbm5lY3Rpb24iCgkJICB9CgkJXSwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvTWFpblNlY3Rpb24vMS8xIgoJCQl9CgkJICBdCgkJfSwKCQkidmFsdWUiOiBbCgkJICB7CgkJCSJpZFNob3J0IjogIkNvbmR1Y3RvckNvbm5lY3Rpb25NZXRob2QiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFTNDU4IzAwMiIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZSI6IFsKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZGVfREUiLAoJCQkJInRleHQiOiAicG5ldW1hdGlzY2hlciBBbnNjaGx1c3MiCgkJCSAgfSwKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZW5fR0IiLAoJCQkJInRleHQiOiAicG5ldW1hdGljIHBvcnQiCgkJCSAgfQoJCQldLAoJCQkibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIlBuZXVtYXRpY1BvcnQiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQUFaOTYwIzAwMSIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIlIxLzgiLAoJCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJCSAgfSwKCQkgIHsKCQkJImlkU2hvcnQiOiAiVHlwZU9mUHJvY2Vzc0Nvbm5lY3Rpb24iLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQkFBMDIxIzAwNyIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZSI6IFsKCQkJICB7CgkJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkJInRleHQiOiAiUjEvOCIKCQkJICB9LAoJCQkgIHsKCQkJCSJsYW5ndWFnZSI6ICJlbiIsCgkJCQkidGV4dCI6ICJSMS84IgoJCQkgIH0KCQkJXSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9CgkJXSwKCQkibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCgkgIH0sCgkgIHsKCQkiaWRTaG9ydCI6ICJIb3VzaW5nX0RpbWVuc2lvbiIsCgkJImRpc3BsYXlOYW1lIjogWwoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZGUiLAoJCQkidGV4dCI6ICJHZWhcdTAwRTR1c2UvQWJtZXNzdW5nZW4iCgkJICB9LAoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkidGV4dCI6ICJIb3VzaW5nL0RpbWVuc2lvbiIKCQkgIH0KCQldLAoJCSJkZXNjcmlwdGlvbiI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiR2VoXHUwMEU0dXNlL0FibWVzc3VuZ2VuIgoJCSAgfSwKCQkgIHsKCQkJImxhbmd1YWdlIjogImVuIiwKCQkJInRleHQiOiAiSG91c2luZy9EaW1lbnNpb24iCgkJICB9CgkJXSwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvTWFpblNlY3Rpb24vMS8xIgoJCQl9CgkJICBdCgkJfSwKCQkidmFsdWUiOiBbCgkJICB7CgkJCSJpZFNob3J0IjogIk1hdGVyaWFsU2VhbGluZyIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1BQUs0NjgjMDA2IgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJNYXRlcmlhbE9mSG91c2luZyIsCgkJCSJzZW1hbnRpY0lkIjogewoJCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkJICAia2V5cyI6IFsKCQkJCXsKCQkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJCSAgInZhbHVlIjogIjAxNzMtMSMwMi1CQUM0NjEjMDE1IgoJCQkJfQoJCQkgIF0KCQkJfSwKCQkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkJICB9LAoJCSAgewoJCQkiaWRTaG9ydCI6ICJOZXRXZWlnaHQiLAoJCQkic2VtYW50aWNJZCI6IHsKCQkJICAidHlwZSI6ICJFeHRlcm5hbFJlZmVyZW5jZSIsCgkJCSAgImtleXMiOiBbCgkJCQl7CgkJCQkgICJ0eXBlIjogIkdsb2JhbFJlZmVyZW5jZSIsCgkJCQkgICJ2YWx1ZSI6ICIwMTczLTEjMDItQkFEODc1IzAwNyIKCQkJCX0KCQkJICBdCgkJCX0sCgkJCSJ2YWx1ZVR5cGUiOiAieHM6c3RyaW5nIiwKCQkJInZhbHVlIjogIjgwIiwKCQkJIm1vZGVsVHlwZSI6ICJQcm9wZXJ0eSIKCQkgIH0sCgkJICB7CgkJCSJpZFNob3J0IjogIkFzc2VtYmx5IiwKCQkJInNlbWFudGljSWQiOiB7CgkJCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJCQkgICJrZXlzIjogWwoJCQkJewoJCQkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCQkJICAidmFsdWUiOiAiMDE3My0xIzAyLUJBQzgxOCMwMTAiCgkJCQl9CgkJCSAgXQoJCQl9LAoJCQkidmFsdWUiOiBbCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImRlIiwKCQkJCSJ0ZXh0IjogIm1pdCBHZXdpbmRlIgoJCQkgIH0sCgkJCSAgewoJCQkJImxhbmd1YWdlIjogImVuIiwKCQkJCSJ0ZXh0IjogIlZpYSB0aHJlYWQiCgkJCSAgfQoJCQldLAoJCQkibW9kZWxUeXBlIjogIk11bHRpTGFuZ3VhZ2VQcm9wZXJ0eSIKCQkgIH0KCQldLAoJCSJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKCSAgfQoJXSwKCSJtb2RlbFR5cGUiOiAiU3VibW9kZWxFbGVtZW50Q29sbGVjdGlvbiIKICB9LAogIHsKCSJpZFNob3J0IjogIkZ1cnRoZXJJbmZvcm1hdGlvbiIsCgkic2VtYW50aWNJZCI6IHsKCSAgInR5cGUiOiAiRXh0ZXJuYWxSZWZlcmVuY2UiLAoJICAia2V5cyI6IFsKCQl7CgkJICAidHlwZSI6ICJHbG9iYWxSZWZlcmVuY2UiLAoJCSAgInZhbHVlIjogImh0dHBzOi8vYWRtaW4tc2hlbGwuaW8vWlZFSS9UZWNobmljYWxEYXRhL0Z1cnRoZXJJbmZvcm1hdGlvbi8xLzEiCgkJfQoJICBdCgl9LAoJInZhbHVlIjogWwoJICB7CgkJImlkU2hvcnQiOiAiVmFsaWREYXRlIiwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvTWFudWZhY3R1cmVyT3JkZXJDb2RlLzEvMSIKCQkJfQoJCSAgXQoJCX0sCgkJInZhbHVlVHlwZSI6ICJ4czpzdHJpbmciLAoJCSJ2YWx1ZSI6ICIyMDIzLTExLTEzIiwKCQkibW9kZWxUeXBlIjogIlByb3BlcnR5IgoJICB9LAoJICB7CgkJImlkU2hvcnQiOiAiVGV4dFN0YXRlbWVudDAxIiwKCQkic2VtYW50aWNJZCI6IHsKCQkgICJ0eXBlIjogIkV4dGVybmFsUmVmZXJlbmNlIiwKCQkgICJrZXlzIjogWwoJCQl7CgkJCSAgInR5cGUiOiAiR2xvYmFsUmVmZXJlbmNlIiwKCQkJICAidmFsdWUiOiAiaHR0cHM6Ly9hZG1pbi1zaGVsbC5pby9aVkVJL1RlY2huaWNhbERhdGEvVGV4dFN0YXRlbWVudC8xLzEiCgkJCX0KCQkgIF0KCQl9LAoJCSJ2YWx1ZSI6IFsKCQkgIHsKCQkJImxhbmd1YWdlIjogImRlIiwKCQkJInRleHQiOiAiKGMpIEZlc3RvIFNFIFx1MDAyNiBDby4gS0c7IFx1MDBDNG5kZXJ1bmdlbiB2b3JiZWhhbHRlbi4iCgkJICB9LAoJCSAgewoJCQkibGFuZ3VhZ2UiOiAiZW4iLAoJCQkidGV4dCI6ICIoYykgRmVzdG8gU0UgXHUwMDI2IENvLiBLRzsgc3ViamVjdCB0byBjaGFuZ2UuIgoJCSAgfQoJCV0sCgkJIm1vZGVsVHlwZSI6ICJNdWx0aUxhbmd1YWdlUHJvcGVydHkiCgkgIH0KCV0sCgkibW9kZWxUeXBlIjogIlN1Ym1vZGVsRWxlbWVudENvbGxlY3Rpb24iCiAgfQpdLAoibW9kZWxUeXBlIjogIlN1Ym1vZGVsIgp9" } ] } \ No newline at end of file diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 326f032ac..a2a0accf5 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -10,6 +10,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxIntegrationBase; using AasxPackageLogic.PackageCentral; using AdminShellNS; +using AdminShellNS.DiaryData; using AnyUi; using Extensions; using Namotion.Reflection; @@ -36,6 +37,11 @@ public interface IManageVisualAasxElements VisualElementGeneric GetSelectedItem(); } + public interface ITaintableIdentifiable + { + DateTime? GetTaintedTime(); + } + public class TreeViewLineCache { public Dictionary IsExpanded = new Dictionary(); @@ -685,7 +691,7 @@ public static void SetCdSortOrderByString(string order) } } - public class VisualElementAdminShell : VisualElementGeneric + public class VisualElementAdminShell : VisualElementGeneric, ITaintableIdentifiable { public AdminShellPackageEnvBase thePackage = null; public Aas.IEnvironment theEnv = null; @@ -722,6 +728,13 @@ public override object GetMainDataObject() return theAas; } + public DateTime? GetTaintedTime() + { + if (theAas is ITaintedData itd && itd.TaintedData?.Tainted != null) + return itd.TaintedData.Tainted; + return null; + } + public override void RefreshFromMainData() { if (theAas != null) @@ -784,7 +797,7 @@ public override void RefreshFromMainData() } } - public class VisualElementSubmodelRef : VisualElementGeneric + public class VisualElementSubmodelRef : VisualElementGeneric, ITaintableIdentifiable { public Aas.IEnvironment theEnv = null; public AdminShellPackageEnvBase thePackage = null; @@ -832,12 +845,23 @@ public override object GetDereferencedMainDataObject() return theSubmodel; } + public DateTime? GetTaintedTime() + { + if (theSubmodel is ITaintedData itd && itd.TaintedData?.Tainted != null) + return itd.TaintedData.Tainted; + return null; + } + public override void RefreshFromMainData() { if (theSubmodel != null) { + var td = ""; + if (GetTaintedTime() != null) + td = "\u273d "; + var ci = theSubmodel.ToCaptionInfo(); - this.Caption = ((theSubmodel.Kind != null && theSubmodel.Kind == Aas.ModellingKind.Template) ? " " : "") + ci.Item1; + this.Caption = td + ((theSubmodel.Kind != null && theSubmodel.Kind == Aas.ModellingKind.Template) ? " " : "") + ci.Item1; this.Info = ci.Item2; } else @@ -845,11 +869,10 @@ public override void RefreshFromMainData() this.Caption = "Missing Aas.Submodel for Reference!"; this.Info = "->" + ((this.theSubmodelRef == null) ? "" : this.theSubmodelRef.ToString()); } - } - + } } - public class VisualElementSubmodel : VisualElementGeneric + public class VisualElementSubmodel : VisualElementGeneric, ITaintableIdentifiable { public Aas.IEnvironment theEnv = null; public Aas.ISubmodel theSubmodel = null; @@ -884,18 +907,75 @@ public override object GetMainDataObject() return theSubmodel; } + public DateTime? GetTaintedTime() + { + if (theSubmodel is ITaintedData itd && itd.TaintedData?.Tainted != null) + return itd.TaintedData.Tainted; + return null; + } + public override void RefreshFromMainData() { if (theSubmodel != null) { + var td = ""; + if (GetTaintedTime() != null) + td = "\u273d "; + var ci = theSubmodel.ToCaptionInfo(); - this.Caption = ((theSubmodel.Kind != null && theSubmodel.Kind == Aas.ModellingKind.Template) ? " " : "") + ci.Item1; + this.Caption = td + ((theSubmodel.Kind != null && theSubmodel.Kind == Aas.ModellingKind.Template) ? " " : "") + ci.Item1; this.Info = ci.Item2; } } } + public class VisualElementSubmodelStub : VisualElementGeneric + { + public AdminShellPackageEnvBase thePackEnv = null; + public AasIdentifiableSideInfo theSideInfo = null; + + public VisualElementSubmodelStub( + VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnvBase packEnv, + AasIdentifiableSideInfo si) + : base() + { + this.Parent = parent; + this.Cache = cache; + this.thePackEnv = packEnv; + this.theSideInfo = si; + + this.Background = new AnyUiColor(0xffd0d0d0u); + this.Border = new AnyUiColor(0xff606060u); + this.TagBg = new AnyUiColor(0xff707070u); + this.TagFg = AnyUiColors.White; + + this.TagString = "SM"; + RefreshFromMainData(); + RestoreFromCache(); + } + + public override string GetFilterElementInfo() + { + return "SubmodelStub"; + } + + public override object GetMainDataObject() + { + return theSideInfo; + } + + public override void RefreshFromMainData() + { + if (theSideInfo != null) + { + this.Caption = "\u2205 " + theSideInfo.Id ; + this.Info = "(Submodel stub)"; + } + } + + } + public class VisualElementReference : VisualElementGeneric { public Aas.IEnvironment theEnv = null; @@ -1930,6 +2010,24 @@ private VisualElementAdminShell GenerateVisuElemForAAS( var sm = env.FindSubmodel(smr); if (sm == null) { + // check if Submodel with only side info is present + if (smr?.IsValid() == true + && env.Submodels is OnDemandListIdentifiable smsi) + { + var ndx = smsi.FindSideInfoIndexFromId(smr.GetAsIdentifier()); + if (ndx >= 0) + { + var si = smsi.GetSideInfo(ndx); + + // add stub + var tiSmSi = new VisualElementSubmodelStub(tiAas, cache, package, si); + tiAas.Members.Add(tiSmSi); + + // not further here!! + continue; + } + } + // notify user Log.Singleton.Error("Cannot find some submodel!"); @@ -2317,20 +2415,6 @@ public void AddVisualElementsFromShellEnv( // if edit mode, then display further .. if (editMode) { - // dead-csharp off - // - // over all assets - // - //foreach (var asset in env.Assets) - //{ - // // item - // var tiAsset = new VisualElementAsset(tiAssets, cache, env, asset); - // tiAssets.Members.Add(tiAsset); - //} - // dead-csharp on - // - // over all Submodels (not the refs) - // var tiAllSubmodels = new VisualElementEnvironmentItem( tiEnv, cache, package, env, VisualElementEnvironmentItem.ItemType.AllSubmodels, mainDataObject: env.Submodels); @@ -2338,11 +2422,28 @@ public void AddVisualElementsFromShellEnv( tiEnv.Members.Add(tiAllSubmodels); // show all Submodels - if (env != null) + // be aware of not loaded / side infos + if (env != null && env.Submodels != null) { - foreach (var sm in env.AllSubmodels()) + for (int smi=0; smi < env.Submodels.Count; smi++) { + // check if Submodel with only side info is present + if (env.Submodels[smi] == null && env.Submodels is OnDemandList smsi) + { + var si = smsi.GetSideInfo(smi); + if (si != null) + { + // add stub + var tiSmSi = new VisualElementSubmodelStub(tiAllSubmodels, cache, package, si); + tiAllSubmodels.Members.Add(tiSmSi); + + // not further here!! + continue; + } + } + // Submodel + var sm = env.Submodels[smi]; var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, env, sm); tiSm.SetIsExpandedIfNotTouched(expandMode > 1); tiAllSubmodels.Members.Add(tiSm); @@ -2461,7 +2562,7 @@ public void ExecuteLazyLoading(VisualElementGeneric ve, bool forceExpanded = fal if (vesf.thePackage != null) { var files = vesf.thePackage.GetListOfSupplementaryFiles(); - foreach (var fi in files) + foreach (var fi in files.ForEachSafe()) ve.Members.Add(new VisualElementSupplementalFile(ve, vesf.Cache, vesf.thePackage, fi)); } @@ -2512,6 +2613,28 @@ public IEnumerable FindAllVisualElement(Predicate FindAllVisualElementTopToIdentifiableInternal(VisualElementGeneric ve) + { + // check if Identifiable + if (ve is VisualElementAdminShell + || ve is VisualElementSubmodel || ve is VisualElementSubmodelRef || ve is VisualElementSubmodelStub + || ve is VisualElementConceptDescription) + yield return ve; + + // top elements to recurse + if (ve is VisualElementEnvironmentItem || ve is VisualElementAdminShell) + foreach (var child in ve.Members) + foreach (var x in FindAllVisualElementTopToIdentifiableInternal(child)) + yield return x; + } + + public IEnumerable FindAllVisualElementTopToIdentifiable() + { + foreach (var tvl in this) + foreach (var e in FindAllVisualElementTopToIdentifiableInternal(tvl)) + yield return e; + } + public IEnumerable FindAllVisualElementOf(Predicate p) where T : VisualElementGeneric { diff --git a/src/AasxPluginDigitalNameplate/NameplateAnyUiControl.cs b/src/AasxPluginDigitalNameplate/NameplateAnyUiControl.cs index 83f9570e3..0d7e9fab8 100644 --- a/src/AasxPluginDigitalNameplate/NameplateAnyUiControl.cs +++ b/src/AasxPluginDigitalNameplate/NameplateAnyUiControl.cs @@ -40,7 +40,7 @@ public class NameplateAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private DigitalNameplateOptions _options = null; private PluginEventStack _eventStack = null; @@ -75,7 +75,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, DigitalNameplateOptions theOptions, PluginEventStack eventStack, @@ -112,7 +112,7 @@ public static NameplateAnyUiControl FillWithAnyUiControls( AasxPluginBase plugin) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) diff --git a/src/AasxPluginDigitalNameplate/NameplateData.cs b/src/AasxPluginDigitalNameplate/NameplateData.cs index eefbc2c0e..4a52cb2b5 100644 --- a/src/AasxPluginDigitalNameplate/NameplateData.cs +++ b/src/AasxPluginDigitalNameplate/NameplateData.cs @@ -63,7 +63,7 @@ public NameplateData() { } // public static string TryGuessAssetId( - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel subModel) { // access @@ -82,7 +82,7 @@ public static string TryGuessAssetId( // public static NameplateData ParseSubmodelForV10( - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel subModel, DigitalNameplateOptions options, string defaultLang = null) { @@ -205,7 +205,7 @@ public static NameplateData ParseSubmodelForV10( // public static NameplateData ParseSubmodelForV20( - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel subModel, DigitalNameplateOptions options, string defaultLang = null) { diff --git a/src/AasxPluginDigitalNameplate/Plugin.cs b/src/AasxPluginDigitalNameplate/Plugin.cs index 20f255c08..acda54c5d 100644 --- a/src/AasxPluginDigitalNameplate/Plugin.cs +++ b/src/AasxPluginDigitalNameplate/Plugin.cs @@ -190,7 +190,7 @@ public class Session : PluginSessionBase if (_sessions.AccessSession(args[0], out Session session)) { // dispose all ressources - session.AnyUiControl.Dispose(); + session.AnyUiControl?.Dispose(); // remove _sessions.Remove(args[0]); diff --git a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs index 9757bed1c..a983a1f69 100644 --- a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs +++ b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs @@ -365,6 +365,13 @@ public IEnumerable FindAllVisualElement(Predicate FindAllVisualElementTopToIdentifiable() + { + if (displayedTreeViewLines != null) + foreach (var ve in displayedTreeViewLines.FindAllVisualElementTopToIdentifiable()) + yield return ve; + } + public bool Contains(VisualElementGeneric ve) { if (displayedTreeViewLines != null) diff --git a/src/BlazorExplorer/Data/BlazorSession.cs b/src/BlazorExplorer/Data/BlazorSession.cs index e6ce41c15..428c6bd66 100644 --- a/src/BlazorExplorer/Data/BlazorSession.cs +++ b/src/BlazorExplorer/Data/BlazorSession.cs @@ -375,7 +375,7 @@ public BlazorSession() ElementPanel = new AnyUiStackPanel() { Orientation = AnyUiOrientation.Vertical }; if (env?.AasEnv?.AssetAdministrationShells != null) - helper.DisplayOrEditAasEntityAas(PackageCentral, env.AasEnv, + helper.DisplayOrEditAasEntityAas(PackageCentral, env, env.AasEnv, env.AasEnv.AllAssetAdministrationShells().FirstOrDefault(), EditMode, ElementPanel, hintMode: HintMode); htmlDotnetThread = new Thread(AnyUiDisplayContextHtml.htmlDotnetLoop); From 64d3181d7f613490bd1c74e3af0ea0ba8e65e92e Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sat, 5 Oct 2024 14:15:12 +0200 Subject: [PATCH 08/99] * intermediate state * UI, fetch does work * fetch next works * work on double clicks --- src/AasxPackageExplorer/MainWindow.xaml.cs | 50 ++- src/AasxPackageExplorer/debug.MIHO.script | 1 + .../options-debug.MIHO.json | 1 + .../DispEditHelperEntities.cs | 311 ++++++++++++++++-- src/AasxPackageLogic/ExplorerMenuFactory.cs | 2 +- src/AasxPackageLogic/IMainWindow.cs | 12 +- .../MainWindowAnyUiDialogs.cs | 5 +- src/AasxPackageLogic/Options.cs | 5 +- .../PackageCentral/AasOnDemandEnvironment.cs | 7 +- .../AdminShellPackageDynamicFetchEnv.cs | 152 ++++++--- .../PackageCentral/PackageContainerFactory.cs | 8 +- .../PackageContainerHttpRepoSubset.cs | 305 +++++++++++++---- .../PackageCentral/PackageHttpDownloadUtil.cs | 9 +- src/AasxPackageLogic/VisualAasxElements.cs | 117 +++++-- .../DiplayVisualAasxElements.xaml | 2 +- .../DiplayVisualAasxElements.xaml.cs | 5 + .../DispEditAasxEntity.xaml.cs | 4 +- src/BlazorExplorer/BlazorVisualElements.cs | 6 + .../Data/BlazorSession.MainWindow.cs | 11 +- src/BlazorExplorer/Data/BlazorSession.cs | 3 +- 20 files changed, 842 insertions(+), 174 deletions(-) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 4109917bb..20b493e79 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -236,11 +236,20 @@ public void RedrawAllAasxElements(bool keepFocus = false, #endif } + /// + /// Checks, if any identifiable is tainted (modified). Helps asking the user if to save + /// data before losing it. + /// + public bool CheckIsAnyTaintedIdentifiableInMain() + { + return DisplayElements.IsAnyTaintedIdentifiable(); + } + /// /// Large extend. Basially redraws everything after new package has been loaded. /// /// Only tghe AUX package has been altered. - public void RestartUIafterNewPackage(bool onlyAuxiliary = false) + public void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null) { if (onlyAuxiliary) { @@ -252,7 +261,7 @@ public void RestartUIafterNewPackage(bool onlyAuxiliary = false) // visually a new content // switch off edit mode -> will will cause the browser to show the AAS as selected element // and -> this will update the left side of the screen correctly! - MainMenu?.SetChecked("EditMenu", false); + MainMenu?.SetChecked("EditMenu", nextEditMode.HasValue ? nextEditMode.Value : false); ClearAllViews(); RedrawAllAasxElements(); RedrawElementView(); @@ -364,6 +373,7 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() /// Already loaded container to take over (alternative 3) /// Store this filename into last recently used list /// Index loaded contents, e.g. for animate of event sending + /// Set the edit mode AFTER loading public void UiLoadPackageWithNew( PackageCentralItem packItem, AdminShellPackageFileBasedEnv takeOverEnv = null, @@ -373,12 +383,19 @@ public void UiLoadPackageWithNew( bool doNotNavigateAfterLoaded = false, PackageContainerBase takeOverContainer = null, string storeFnToLRU = null, - bool indexItems = false) + bool indexItems = false, + bool preserveEditMode = false, + bool? nextEditMode = null) { // access if (packItem == null) return; + // do a bit logic for easy calling via IMainWindow + if (preserveEditMode) + nextEditMode = MainMenu?.IsChecked("EditMenu") == true; + + // start loading new stuff if (loadLocalFilename != null) { if (info == null) @@ -420,7 +437,7 @@ public void UiLoadPackageWithNew( // displaying try { - RestartUIafterNewPackage(onlyAuxiliary); + RestartUIafterNewPackage(onlyAuxiliary, nextEditMode); } catch (Exception ex) { @@ -604,6 +621,7 @@ public void PrepareDispEditEntity( PackageCentral, DisplayContext, entities, editMode, hintMode, showIriMode, checkSmt, tiCds?.CdSortOrder, flyoutProvider: this, + mainWindow: this, appEventProvider: this, hightlightField: hightlightField, superMenu: DynamicMenu.Menu); @@ -2914,14 +2932,28 @@ private void DisplayElements_SelectedItemChanged(object sender, EventArgs e) private void DisplayElements_MouseDoubleClick(object sender, MouseButtonEventArgs e) { // we're assuming, that SelectedItem point to the right business object - if (DisplayElements.SelectedItem == null) + var si = DisplayElements.SelectedItem; + if (si == null) return; - // redraw view - RedrawElementView(); + // act depending on selectedItem - // "simulate" click on "ShowContents" - this.ShowContent_Click(this.ShowContent, null); + if (si is VisualElementEnvironmentItem siei + && (siei.theItemType == VisualElementEnvironmentItem.ItemType.FetchPrev + || siei.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext)) + { + // want to refetch elements + ; + } + else + if (si is VisualElementSubmodelElement) + { + // redraw view + RedrawElementView(); + + // "simulate" click on "ShowContents" + this.ShowContent_Click(this.ShowContent, null); + } } private void Window_Closing(object sender, CancelEventArgs e) diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 86108d204..74750c9c5 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,6 +9,7 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); +Tool("connectextended"); Select("Submodel", "First"); // Select("Submodel", "Next"); // Tool("exportsmtasciidoc", "File", "C:\Users\homi0002\Desktop\tmp\new.zip", "ExportHtml", "true", "ExportPdf", "false", "AntoraStyle", "false", "ViewResult", "true"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index e9fc3f993..c1fa7d883 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -102,6 +102,7 @@ "EclassTwoPass": true, "BackupDir": ".\\backup", "BackupFiles": 10, + "MaxParallelOps": 20, "RestServerHost": "localhost", "RestServerPort": "1111", "WriteDefaultOptionsFN": null /* "options-written.json" */, diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index aaf24b8ee..9320f974f 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -26,6 +26,9 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasCore.Samm2_2_0; using static AasxPackageLogic.DispEditHelperBasics; using System.Windows.Controls; +using AasxPackageExplorer; +using System.Threading.Tasks; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; namespace AasxPackageLogic { @@ -427,7 +430,8 @@ public void DisplayOrEditAasEntityAasEnv( PackageCentral.PackageCentral packages, Aas.IEnvironment env, VisualElementEnvironmentItem ve, bool editMode, AnyUiStackPanel stack, bool hintMode = false, - AasxMenu superMenu = null) + AasxMenu superMenu = null, + IMainWindow mainWindow = null) { this.AddGroup(stack, "Environment of AssetInformation Administration Shells", this.levelColors.MainSection); if (env == null) @@ -1119,6 +1123,109 @@ public void DisplayOrEditAasEntityAasEnv( }); stack.Children.Add(g); } + else if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext) + { + // check all pre-requisites + if (!(context is AnyUiContextPlusDialogs plusDialogs + && ve.thePackage is AdminShellPackageDynamicFetchEnv dynPack + && dynPack.GetContext() is PackageContainerHttpRepoSubsetFetchContext fetchContext + && fetchContext.Record != null + && mainWindow != null)) + { + AddHintBubble(stack, hintMode, new HintCheck( + () => true, + "Not enough data to provide dynamic fetch operations.", + severityLevel: HintCheck.Severity.High)); + return; + } + + // at the end? + if (fetchContext.Cursor?.HasContent() != true) + { + AddHintBubble(stack, hintMode, new HintCheck( + () => true, + "No further fetch operation available " + + "(at the end of the selected subset of elements?).", + severityLevel: HintCheck.Severity.Notice)); + return; + } + + // go ahead + AddHintBubble(stack, hintMode, new HintCheck( + () => true, + "The entities in this structure were fetched dynamically from " + + "endpoints such as registries and repositories. This fetch could " + + "be advanced to the next set of elements.", + severityLevel: HintCheck.Severity.Notice)); + + AddActionPanel(stack, "Actions:", + repo: repo, + superMenu: superMenu, + ticketMenu: new AasxMenu() + .AddAction("fetch-next", "Fetch next", + "Fetch the next set of elements."), + ticketActionAsync: async (buttonNdx, ticket) => + { + //await Task.Yield(); + + //if (buttonNdx == 0) + //{ + // // check if something is tainted + // if (mainWindow?.CheckIsAnyTaintedIdentifiableInMain() == true) + // { + // if (AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( + // "There are unsafed data changes in Identifiables. A fetch of elements " + + // "might result in data loss.", + // "Proceed with fetch?", + // AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + // return new AnyUiLambdaActionNone(); + // } + + // // modify (!) record data to do no skip anymore, using cursor data + // fetchContext.Record.PageSkip = 0; + // var location = PackageContainerHttpRepoSubset.BuildLocationFrom( + // fetchContext.Record, fetchContext.Cursor); + // if (location == null) + // { + // MainWindowLogic.LogErrorToTicketStatic(ticket, + // new InvalidDataException(), + // "Error building location from next fetch selection. Aborting."); + // return new AnyUiLambdaActionNone(); + // } + + // // more details into container options + // var containerOptions = new PackageContainerHttpRepoSubset. + // PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + // fetchContext.Record); + + // // load + // Log.Singleton.Info($"For refining extended connect, loading " + + // $"from {location} into container"); + + // var container = await PackageContainerFactory.GuessAndCreateForAsync( + // packages, + // location, + // location, + // overrideLoadResident: true, + // containerOptions: containerOptions, + // runtimeOptions: packages.CentralRuntimeOptions); + + // if (container == null) + // Log.Singleton.Error($"Failed to load from {location}"); + // else + // mainWindow.UiLoadPackageWithNew(packages.MainItem, + // takeOverContainer: container, onlyAuxiliary: false, indexItems: true, + // storeFnToLRU: location, + // nextEditMode: editMode); + + // Log.Singleton.Info($"Successfully loaded {location}"); + //} + //return new AnyUiLambdaActionNone(); + + return await ExecuteUiForFetchOfElements + ("fetch-next", packages, context, editMode, ticket, mainWindow, fetchContext); + }); + } else { // Default @@ -1144,16 +1251,91 @@ public void DisplayOrEditAasEntityAasEnv( // overview information var g = this.AddSmallGrid( - 6, 1, new[] { "*" }, margin: new AnyUiThickness(5, 5, 0, 0)); + 6, 1, new[] { "*" }, margin: new AnyUiThickness(5, 5, 0, 0)); this.AddSmallLabelTo( - g, 0, 0, content: "This structure hold the main entites of Administration shells."); + g, 0, 0, content: "This structure holds the main entities of Administration shells."); this.AddSmallLabelTo( - g, 1, 0, content: String.Format("#admin shells: {0}.", env.AssetAdministrationShellCount()), + g, 1, 0, content: String.Format("#AssetAdministrationShells: {0}.", env.AssetAdministrationShellCount()), margin: new AnyUiThickness(0, 5, 0, 0)); - this.AddSmallLabelTo(g, 3, 0, content: String.Format("#submodels: {0}.", env.SubmodelCount())); + this.AddSmallLabelTo(g, 3, 0, content: String.Format("#Submodels: {0}.", env.SubmodelCount())); this.AddSmallLabelTo( - g, 4, 0, content: String.Format("#concept descriptions: {0}.", env.ConceptDescriptionCount())); + g, 4, 0, content: String.Format("#ConceptDescriptions: {0}.", env.ConceptDescriptionCount())); stack.Children.Add(g); + + // dynamic fetched + if (ve.thePackage is AdminShellPackageDynamicFetchEnv dynPack + && mainWindow != null) + { + AddHintBubble(stack, hintMode, new HintCheck( + () => true, + "The entities in this structure were fetched dynamically from " + + "endpoints such as registries and repositories.", + severityLevel: HintCheck.Severity.Notice)); + + AddActionPanel(stack, "Actions:", + repo: repo, + superMenu: superMenu, + ticketMenu: new AasxMenu() + .AddAction("refine-fetch", "Refine fetch ..", + "Refine the fetch parameters which led to this dynamic set of elements."), + ticketActionAsync: async (buttonNdx, ticket) => + { + if (buttonNdx == 0 + && context is AnyUiContextPlusDialogs plusDialogs) + { + // default, but better: used record + var record = (((ve.thePackage as AdminShellPackageDynamicFetchEnv)?.GetContext() + as PackageContainerHttpRepoSubsetFetchContext)?.Record + as ConnectExtendedRecord) + ?? new PackageContainerHttpRepoSubset.ConnectExtendedRecord(); + + var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( + ticket, plusDialogs, + "Connect AAS repositories and registries", + record); + + if (!uiRes) + return new AnyUiLambdaActionNone(); ; + + var location = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + if (location == null) + { + MainWindowLogic.LogErrorToTicketStatic(ticket, + new InvalidDataException(), + "Error building location from query selection. Aborting."); + return new AnyUiLambdaActionNone(); + } + + // more details into container options + var containerOptions = new PackageContainerHttpRepoSubset. + PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + record); + + // load + Log.Singleton.Info($"For refining extended connect, loading " + + $"from {location} into container"); + + var container = await PackageContainerFactory.GuessAndCreateForAsync( + packages, + location, + location, + overrideLoadResident: true, + containerOptions: containerOptions, + runtimeOptions: packages.CentralRuntimeOptions); + + if (container == null) + Log.Singleton.Error($"Failed to load from {location}"); + else + mainWindow.UiLoadPackageWithNew(packages.MainItem, + takeOverContainer: container, onlyAuxiliary: false, indexItems: true, + storeFnToLRU: location, + nextEditMode: editMode); + + Log.Singleton.Info($"Successfully loaded {location}"); + } + return new AnyUiLambdaActionNone(); + }); + } } } @@ -1228,6 +1410,79 @@ public void DisplayOrEditAasEntitySupplementaryFile( } } + // + // + // --- Dynamic fetch of elements + // + // + + public static async Task ExecuteUiForFetchOfElements( + string mode, + PackageCentral.PackageCentral packages, + AnyUiContextBase displayContext, + bool editMode, + AasxMenuActionTicket ticket, + IMainWindow mainWindow, + PackageContainerHttpRepoSubsetFetchContext fetchContext) + { + await Task.Yield(); + + if (mode == "fetch-next") + { + // check if something is tainted + if (mainWindow?.CheckIsAnyTaintedIdentifiableInMain() == true) + { + if (AnyUiMessageBoxResult.Yes != displayContext.MessageBoxFlyoutShow( + "There are unsafed data changes in Identifiables. A fetch of elements " + + "might result in data loss.", + "Proceed with fetch?", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + return new AnyUiLambdaActionNone(); + } + + // modify (!) record data to do no skip anymore, using cursor data + fetchContext.Record.PageSkip = 0; + var location = PackageContainerHttpRepoSubset.BuildLocationFrom( + fetchContext.Record, fetchContext.Cursor); + if (location == null) + { + MainWindowLogic.LogErrorToTicketStatic(ticket, + new InvalidDataException(), + "Error building location from next fetch selection. Aborting."); + return new AnyUiLambdaActionNone(); + } + + // more details into container options + var containerOptions = new PackageContainerHttpRepoSubset. + PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + fetchContext.Record); + + // load + Log.Singleton.Info($"For refining extended connect, loading " + + $"from {location} into container"); + + var container = await PackageContainerFactory.GuessAndCreateForAsync( + packages, + location, + location, + overrideLoadResident: true, + containerOptions: containerOptions, + runtimeOptions: packages.CentralRuntimeOptions); + + if (container == null) + Log.Singleton.Error($"Failed to load from {location}"); + else + mainWindow.UiLoadPackageWithNew(packages.MainItem, + takeOverContainer: container, onlyAuxiliary: false, indexItems: true, + storeFnToLRU: location, + nextEditMode: editMode); + + Log.Singleton.Info($"Successfully loaded {location}"); + } + + return new AnyUiLambdaActionNone(); + } + // // // --- AAS @@ -1511,22 +1766,34 @@ public void DisplayOrEditAasEntityAas( .AddAction("stub-load-submodels", "Submodels", "Load missing Submodels only for this AAS.") .AddAction("stub-load-concepts", "ConceptDescriptions", - "Load missing ConceptDescriptions only for this AAS."), + "Load missing ConceptDescriptions only for this AAS.") + .AddAction("stub-load-thumbnail", "Thumbnail", + "Load missing Thumbnail only for this AAS."), ticketActionAsync: async (buttonNdx, ticket) => { - List lrs = null; + if (buttonNdx >= 0 && buttonNdx <= 1) + { - if (buttonNdx == 0) - lrs = aas?.FindAllSubmodelReferences().ToList(); - + List lrs = null; - if (buttonNdx == 1) - lrs = env.FindAllSemanticIdsForAas(aas).ToList(); + if (buttonNdx == 0) + lrs = aas?.FindAllSubmodelReferences().ToList(); - if (lrs != null) - { - var ids = lrs.Select((lr) => (lr?.Reference?.IsValid() == true) ? lr.Reference.Keys[0].Value : null).ToList(); - var fetched = await dynPack.TryFetchSpecificIds(ids); + if (buttonNdx == 1) + lrs = env.FindAllSemanticIdsForAas(aas).ToList(); + + if (lrs != null) + { + var ids = lrs.Select((lr) => (lr?.Reference?.IsValid() == true) ? lr.Reference.Keys[0].Value : null).ToList(); + var fetched = await dynPack.TryFetchSpecificIds(ids); + if (fetched) + return new AnyUiLambdaActionRedrawAllElements(nextFocus: aas); + } + } + + if (buttonNdx == 2) + { + var fetched = await dynPack.TryFetchThumbnail(aas); if (fetched) return new AnyUiLambdaActionRedrawAllElements(nextFocus: aas); } @@ -4921,19 +5188,25 @@ public void DisplayOrEditAasEntitySubmodelElement( } + /// + /// Super function to basically edit all known visual elements. + /// Note: With hesitation, the mainWindow is passed into this function and shall only + /// be used in exceptional cases. + /// public bool DisplayOrEditCommonEntity( PackageCentral.PackageCentral packages, AnyUiStackPanel stack, AasxMenu superMenu, bool editMode, bool hintMode, bool checkSmt, VisualElementEnvironmentItem.ConceptDescSortOrder? cdSortOrder, - VisualElementGeneric entity) + VisualElementGeneric entity, + IMainWindow mainWindow) { if (entity is VisualElementEnvironmentItem veei) { DisplayOrEditAasEntityAasEnv( packages, veei.theEnv, veei, editMode, stack, hintMode: hintMode, - superMenu: superMenu); + superMenu: superMenu, mainWindow: mainWindow); } else if (entity is VisualElementAdminShell veaas) { diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index aa033538e..c2d9181a3 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -53,7 +53,7 @@ public static AasxMenu CreateMainMenu() args: new AasxMenuListOfArgDefs() .Add("File", "Source filename including a path and extension.")) .AddWpfBlazor(name: "ConnectIntegrated", header: "Connect …", inputGesture: "Ctrl+Shift+I") - .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …", inputGesture: "Ctrl+Shift+E") + .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …") .AddWpfBlazor(name: "Save", header: "_Save", inputGesture: "Ctrl+S") .AddWpfBlazor(name: "SaveAs", header: "_Save as …", help: "Saves current package to given file name and typr", diff --git a/src/AasxPackageLogic/IMainWindow.cs b/src/AasxPackageLogic/IMainWindow.cs index 181f2d94b..59f239343 100644 --- a/src/AasxPackageLogic/IMainWindow.cs +++ b/src/AasxPackageLogic/IMainWindow.cs @@ -96,11 +96,17 @@ void RedrawAllAasxElements( /// public void RedrawAllElementsAndFocus(object nextFocus = null, bool isExpanded = true); + /// + /// Checks, if any identifiable is tainted (modified). Helps asking the user if to save + /// data before losing it. + /// + bool CheckIsAnyTaintedIdentifiableInMain(); + /// /// Large extend. Basially redraws everything after new package has been loaded. /// /// Only tghe AUX package has been altered. - void RestartUIafterNewPackage(bool onlyAuxiliary = false); + void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null); /// /// This function serve as a kind of unified contact point for all kind @@ -125,7 +131,9 @@ void UiLoadPackageWithNew( bool doNotNavigateAfterLoaded = false, PackageContainerBase takeOverContainer = null, string storeFnToLRU = null, - bool indexItems = false); + bool indexItems = false, + bool preserveEditMode = false, + bool? nextEditMode = null); /// /// Check for menu switch and flush events, if required. diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index d11418349..c5f9cdaec 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -513,7 +513,7 @@ await val.PerformDialogue(ticket, DisplayContext, var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( ticket, DisplayContext, - "Connect AAS Repository", + "Connect AAS repositories and registries", record); if (!uiRes) @@ -548,7 +548,8 @@ await val.PerformDialogue(ticket, DisplayContext, else MainWindow.UiLoadPackageWithNew(PackageCentral.MainItem, takeOverContainer: container, onlyAuxiliary: false, indexItems: true, - storeFnToLRU: location); + storeFnToLRU: location, + preserveEditMode: true) ; Log.Singleton.Info($"Successfully loaded {location}"); } diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index 3f2b4e99b..c58fae35b 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -438,9 +438,12 @@ public class OptionsInformation Cmd = "-backupdir", Arg = "")] public string BackupDir = null; - [OptionDescription(Description = "At max such much different files are used for backing up")] + [OptionDescription(Description = "At max such much different files are used for backing up.")] public int BackupFiles = 10; + [OptionDescription(Description = "Maximum parallel operations, such as HTTP downloads.")] + public int MaxParallelOps = 20; + [OptionDescription(Description = "If set, load and store AASX files via temporary package to " + "avoid corruptions. RECOMMENDED!", Cmd = "-indirect-load-save")] diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index bca5f5b99..e73da3b3e 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -30,17 +30,20 @@ This source code may use other Open Source software components (see LICENSE.txt) namespace AasxPackageLogic.PackageCentral { - public enum AasIdentifiableSideInfoLevel { IdOnly, IdAndMore, IdWithEndpoint }; + public enum AasIdentifiableSideInfoLevel { None, IdOnly, IdAndMore, IdWithEndpoint }; /// /// This side information helps managing Identifiables, which are not already loaded. /// public class AasIdentifiableSideInfo : OnDemandSideInfoBase { - public AasIdentifiableSideInfoLevel Level = AasIdentifiableSideInfoLevel.IdOnly; + public bool IsStub = false; + public AasIdentifiableSideInfoLevel StubLevel = AasIdentifiableSideInfoLevel.None; public string Id = ""; public string IdShort = ""; + + public bool ShowCursorBelow = false; } /// diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index b3882ff48..0a3467fd1 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -27,9 +27,18 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Linq; using AdminShellNS.DiaryData; using System.Text.Json; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; namespace AasxPackageLogic.PackageCentral { + /// + /// Base class for storing further data which led to the creation of this dynamic + /// set of elements + /// + public class AdminShellPackageDynamicFetchContextBase + { + } + /// /// This class creates a package env, which can handle dynamic loading of elements /// @@ -39,6 +48,10 @@ public class AdminShellPackageDynamicFetchEnv : AdminShellPackageEnvBase protected Uri _defaultRepoBaseUri = null; + protected AdminShellPackageDynamicFetchContextBase _context = null; + public AdminShellPackageDynamicFetchContextBase GetContext() => _context; + public void SetContext(AdminShellPackageDynamicFetchContextBase cursor) { _context = cursor; } + protected Dictionary _thumbStreamPerAasId = new Dictionary(); public AdminShellPackageDynamicFetchEnv( @@ -62,57 +75,96 @@ public override bool IsOpen } } - public async Task FindOrFetchIdentifiable(string id) + public async Task TryFetchThumbnail(Aas.IAssetAdministrationShell aas) { // access - if (id?.HasContent() != true - || !(_aasEnv is AasOnDemandEnvironment odEnv)) - return null; + if (aas?.Id?.HasContent() != true) + return false; - // find existing? - Aas.IIdentifiable idf = _aasEnv?.FindAasById(id); - idf = idf ?? _aasEnv?.FindSubmodelById(id); - idf = idf ?? _aasEnv?.FindConceptDescriptionById(id); - if (idf != null) - return idf; - - // try locate id in Submodels? - var sms = _aasEnv?.Submodels as OnDemandListIdentifiable; - var smndx = sms?.FindSideInfoIndexFromId(id); - if (smndx.HasValue && smndx.Value >= 0) + // try + try { - // directly use id to fetch Identifiable - Aas.IIdentifiable res = null; - - // build the location - var loc = PackageContainerHttpRepoSubset.BuildUriForSubmodel(_defaultRepoBaseUri, id); - if (loc == null) - return null; - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: loc, - allowFakeResponses: _runtimeOptions?.AllowFakeResponses ?? false, + sourceUri: PackageContainerHttpRepoSubset.BuildUriForAasThumbnail(_defaultRepoBaseUri, aas.Id), + runtimeOptions: _runtimeOptions, lambdaDownloadDone: (ms, contentFn) => { - try - { - // load? - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - var sm = Jsonization.Deserialize.SubmodelFrom(node); + AddThumbnail(aas.Id, ms.ToArray()); + }); - // replace, side info will go null - sms[smndx.Value] = sm; + // happy + return true; + } + catch (Exception ex) + { + LogInternally.That.CompletelyIgnoredError(ex); + } - // ok - res = sm; - } - catch (Exception ex) - { - _runtimeOptions?.Log?.Error(ex, "Parsing on demand loaded Submodel"); - } - }); + return false; + } + + public async Task FindOrFetchIdentifiable(string id) + { + // access + if (id?.HasContent() != true + || !(_aasEnv is AasOnDemandEnvironment odEnv)) + return null; - return res; + // try + try + { + // find existing? + Aas.IIdentifiable idf = _aasEnv?.FindAasById(id); + idf = idf ?? _aasEnv?.FindSubmodelById(id); + idf = idf ?? _aasEnv?.FindConceptDescriptionById(id); + if (idf != null) + return idf; + + // try locate id in Submodels? + var sms = _aasEnv?.Submodels as OnDemandListIdentifiable; + var smndx = sms?.FindSideInfoIndexFromId(id); + if (smndx.HasValue && smndx.Value >= 0) + { + // directly use id to fetch Identifiable + Aas.IIdentifiable res = null; + + // build the location + var loc = PackageContainerHttpRepoSubset.BuildUriForSubmodel(_defaultRepoBaseUri, id); + if (loc == null) + return null; + + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: loc, + allowFakeResponses: _runtimeOptions?.AllowFakeResponses ?? false, + runtimeOptions: _runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => + { + try + { + // load? + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + var sm = Jsonization.Deserialize.SubmodelFrom(node); + + // replace, side info will go null + lock (_aasEnv.Submodels) + { + sms[smndx.Value] = sm; + } + + // ok + res = sm; + } + catch (Exception ex) + { + _runtimeOptions?.Log?.Error(ex, "Parsing on demand loaded Submodel"); + } + }); + + return res; + } + } catch (Exception ex) + { + LogInternally.That.CompletelyIgnoredError(ex); } // nope @@ -122,11 +174,11 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( public async Task TryFetchSpecificIds(IEnumerable ids) { var someFetched = false; - if (false) + if (true) { - // buggy + // parallel await Parallel.ForEachAsync( - ids, new ParallelOptions() { MaxDegreeOfParallelism = 10 }, + ids, new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, async (id, token) => { var idf = await FindOrFetchIdentifiable(id); @@ -282,10 +334,14 @@ public void AddThumbnail(string aasId, byte[] content) { if (aasId?.HasContent() != true) return; - if (_thumbStreamPerAasId.ContainsKey(aasId)) - _thumbStreamPerAasId[aasId] = content; - else - _thumbStreamPerAasId.Add(aasId, content); + + lock (_thumbStreamPerAasId) + { + if (_thumbStreamPerAasId.ContainsKey(aasId)) + _thumbStreamPerAasId[aasId] = content; + else + _thumbStreamPerAasId.Add(aasId, content); + } } public byte[] GetThumbnail(string aasId) diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs index 304b74092..4d32910b7 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs @@ -17,6 +17,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxIntegrationBase; using AdminShellNS; using AnyUi; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; namespace AasxPackageLogic.PackageCentral { @@ -214,11 +215,16 @@ public static async Task GuessAndCreateForAsync( if (guess.GuessedType == typeof(PackageContainerHttpRepoSubset)) { + // prepare (extend) container options + var extCntOpt = new PackageContainerHttpRepoSubsetOptions(containerOptions, + new ConnectExtendedRecord()); + + // prepare runtime options runtimeOptions.AllowFakeResponses = false; var cnt = await PackageContainerHttpRepoSubset.CreateAndLoadAsync( packageCentral, location, fullItemLocation, overrideLoadResident, takeOver: takeOver, - containerOptions: containerOptions, runtimeOptions: runtimeOptions); + containerOptions: extCntOpt, runtimeOptions: runtimeOptions); return cnt; } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 49f5a1203..c47ab2f9d 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -26,9 +26,22 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Collections.Generic; using AasxPackageExplorer; using AnyUi; +using Microsoft.Win32; +using Namotion.Reflection; +using System.Text.Json.Nodes; +using System.Linq; namespace AasxPackageLogic.PackageCentral { + /// + /// Context data to fetched packages to remember record, cursor and more + /// + public class PackageContainerHttpRepoSubsetFetchContext : AdminShellPackageDynamicFetchContextBase + { + public PackageContainerHttpRepoSubset.ConnectExtendedRecord Record; + public string Cursor; + } + /// /// This container represents a subset of AAS elements retrieved from a HTTP / networked repository. /// @@ -44,6 +57,9 @@ public override string Location get { return _location; } set { SetNewLocation(value); OnPropertyChanged("InfoLocation"); } } + + public AdminShellPackageDynamicFetchEnv EnvDynPack { get => Env as AdminShellPackageDynamicFetchEnv; } + // // Constructors // @@ -124,14 +140,14 @@ public override string ToString() public static bool IsValidUriForAllAAS(string location) { - var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells(|/)$", + var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } public static bool IsValidUriForSingleAAS(string location) { - var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells/(.{1,99})$", + var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells/([^?]{1,99})", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } @@ -217,6 +233,28 @@ public static Uri CombineUri(Uri baseUri, string relativeUri) return null; } + public static Uri BuildUriForAllAAS(Uri baseUri, int pageLimit = 100, string cursor = null) + { + // access + if (baseUri == null) + return null; + + // try combine + // see: https://code-maze.com/how-to-create-a-url-query-string/ + // (not an simple & obvious approach even with Uri/ UriBuilder) + var uri = new UriBuilder(CombineUri(baseUri, $"shells")); + if (pageLimit > 0) + uri.Query = $"Limit={pageLimit:D}"; + if (cursor != null) + uri.Query += $"&Cursor={cursor}"; + + // Note: cursor comes from the internet (server?) and is used unmodified, simply + // trusted to be continous string and/ or BASE64 encoded. This is not checked, so + // theoretically, a MITM attack could modify the cursor to modify this query!! + + return uri.Uri; + } + public static Uri BuildUriForAAS(Uri baseUri, string id, bool encryptIds = true) { // access @@ -292,9 +330,6 @@ public override async Task LoadFromSourceAsync( PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) { - //PackageHttpDownloadUtil.TryLoadFakeRequests(Assembly.GetExecutingAssembly(), - // "AasxPackageLogic.Resources.PackageContainerFakeAnswers.json"); - var allowFakeResponses = runtimeOptions?.AllowFakeResponses ?? false; var baseUri = GetBaseUri(fullItemLocation); @@ -319,12 +354,77 @@ public override async Task LoadFromSourceAsync( // get the record data var record = (containerOptions as PackageContainerHttpRepoSubsetOptions)?.Record; - // start with AAS? + // invalidate cursor data + string cursor = null; + + // start with a list of AAS? + if (IsValidUriForAllAAS(fullItemLocation)) + { + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + if (node["result"] is JsonArray resChilds + && resChilds.Count > 0) + { + int childsToSkip = Math.Max(0, record.PageSkip); + + foreach (var n2 in resChilds) + // second try to reduce side effects + try + { + if (childsToSkip > 0) + { + childsToSkip--; + continue; + } + + // on last child, attach side info for fetch next cursor + var lastChildAndMore = n2 == resChilds.Last() && record.PageLimit > 0; + var si = (!lastChildAndMore) ? null + : new AasIdentifiableSideInfo() + { + IsStub = false, + ShowCursorBelow = true + }; + + // add + prepAas.Add( + Jsonization.Deserialize.AssetAdministrationShellFrom(n2), + si); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing single AAS of list of all AAS"); + } + } + + // cursor data + if (node["paging_metadata"] is JsonNode nodePaging + && nodePaging["cursor"] is JsonNode nodeCursor) + { + cursor = nodeCursor.ToString(); + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing list of all AAS"); + } + }); + } + + // start with single AAS? if (IsValidUriForSingleAAS(fullItemLocation)) { await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, lambdaDownloadDone: (ms, contentFn) => { try @@ -344,6 +444,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, lambdaDownloadDone: (ms, contentFn) => { try @@ -364,6 +465,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, lambdaDownloadDone: (ms, contentFn) => { try @@ -380,56 +482,73 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // start auto-load missing Submodels? if (record?.AutoLoadSubmodels ?? false) - foreach (var lr in env.FindAllSubmodelReferences(onlyNotExisting: true)) - { - if (record?.AutoLoadOnDemand ?? true) - { - // side info level 1 - prepSM.Add(null, new AasIdentifiableSideInfo() { - Level = AasIdentifiableSideInfoLevel.IdOnly, - Id = lr.Reference.Keys[0].Value - }); - } - else + { + var lrs = env.FindAllSubmodelReferences(onlyNotExisting: true).ToList(); + + await Parallel.ForEachAsync(lrs, + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (lr, token) => { - // no side info => full element - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: BuildUriForSubmodel(baseUri, lr.Reference), - allowFakeResponses: allowFakeResponses, - lambdaDownloadDone: (ms, contentFn) => + if (record?.AutoLoadOnDemand ?? true) { - try + // side info level 1 + lock (prepSM) { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - env.Add(Jsonization.Deserialize.SubmodelFrom(node)); + prepSM.Add(null, new AasIdentifiableSideInfo() + { + IsStub = true, + StubLevel = AasIdentifiableSideInfoLevel.IdOnly, + Id = lr.Reference.Keys[0].Value + }); } - catch (Exception ex) + } + else + { + // no side info => full element + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: BuildUriForSubmodel(baseUri, lr.Reference), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => { - runtimeOptions?.Log?.Error(ex, "Parsing auto-loaded Submodel"); - } - }); - } - } + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + lock (prepSM) + { + prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing auto-loaded Submodel"); + } + }); + } + }); + } // start auto-load missing thumbnails? - if (true) - foreach (var aas in env.AllAssetAdministrationShells()) - { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: BuildUriForAasThumbnail(baseUri, aas.Id), - allowFakeResponses: allowFakeResponses, - lambdaDownloadDone: (ms, contentFn) => - { - try - { - dynPack.AddThumbnail(aas.Id, ms.ToArray()); - } - catch (Exception ex) + if (record?.AutoLoadThumbnails ?? false) + await Parallel.ForEachAsync(env.AllAssetAdministrationShells(), + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (aas, token) => { + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: BuildUriForAasThumbnail(baseUri, aas.Id), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => { - runtimeOptions?.Log?.Error(ex, "Managing auto-loaded tumbnail"); - } - }); - } + try + { + dynPack.AddThumbnail(aas.Id, ms.ToArray()); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Managing auto-loaded tumbnail"); + } + }); + }); // remove, what is not need if (env.AssetAdministrationShellCount() < 1) @@ -442,6 +561,11 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // commit Env = dynPack; Env.SetEnvironment(env); + EnvDynPack?.SetContext(new PackageContainerHttpRepoSubsetFetchContext() + { + Record = record, + Cursor = cursor + }); } public override async Task SaveLocalCopyAsync( @@ -477,13 +601,19 @@ public override async Task SaveToSourceAsync(string saveAsNewFileName = null, // UI // - public class ConnectExtendedRecord + public class ConnectExtendedRecord { + public enum BaseTypeEnum { Repository, Registry } + public static string[] BaseTypeEnumNames = new[] { "Repository", "Registry" }; + public string BaseAddress = "https://eis-data.aas-voyager.com/"; + // public string BaseAddress = "http://smt-repo.admin-shell-io.com/"; + + public BaseTypeEnum BaseType = BaseTypeEnum.Repository; - public bool GetAllAas; + public bool GetAllAas = true; - public bool GetSingleAas = true; + public bool GetSingleAas; public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; public bool GetSingleSubmodel; @@ -502,6 +632,9 @@ public class ConnectExtendedRecord public bool EncryptIds = true; public bool StayConnected; + public int PageLimit = 6; + public int PageSkip = 0; + public void SetQueryChoices(int choice) { GetAllAas = (choice == 1); @@ -521,13 +654,19 @@ public PackageContainerHttpRepoSubsetOptions( LoadResident = baseOpt.LoadResident; StayConnected = baseOpt.StayConnected; UpdatePeriod = baseOpt.UpdatePeriod; - Record = record; + + if (baseOpt is PackageContainerHttpRepoSubsetOptions fullOpt) + Record = fullOpt.Record?.Copy(); + else + Record = record; } public ConnectExtendedRecord Record; } - public static string BuildLocationFrom(ConnectExtendedRecord record) + public static string BuildLocationFrom( + ConnectExtendedRecord record, + string cursor = null) { // access if (record == null || record.BaseAddress?.HasContent() != true) @@ -538,7 +677,9 @@ public static string BuildLocationFrom(ConnectExtendedRecord record) // All AAS? if (record.GetAllAas) { - + // if a skip has been requested, these AAS need to be loaded, as well + var uri = BuildUriForAllAAS(baseUri, record.PageLimit + record.PageSkip, cursor); + return uri.ToString(); } // Single AAS? @@ -646,19 +787,32 @@ public static async Task PerformConnectExtendedDialogue( // dynamic rows int row = 0; - // Base address + // Base address + Type helper.AddSmallLabelTo(g, row, 0, content: "Base address:", verticalAlignment: AnyUiVerticalAlignment.Center, verticalContentAlignment: AnyUiVerticalAlignment.Center); + var g2 = helper.AddSmallGridTo(g, row, 1, 1, 2, new[] { "#", "*" }); + + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 0, + items: ConnectExtendedRecord.BaseTypeEnumNames, + selectedIndex: (int)record.BaseType, + margin: new AnyUiThickness(0, 0, 5, 0), + padding: new AnyUiThickness(0, -1, 0, -3)), + minWidth: 200, maxWidth: 200), + (i) => { record.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); + AnyUiUIElement.SetStringFromControl( helper.Set( - helper.AddSmallTextBoxTo(g, row, 1, + helper.AddSmallTextBoxTo(g2, 0, 1, text: $"{record.BaseAddress}", verticalAlignment: AnyUiVerticalAlignment.Center, verticalContentAlignment: AnyUiVerticalAlignment.Center), horizontalAlignment: AnyUiHorizontalAlignment.Stretch), (s) => { record.BaseAddress = s; }); + row++; // All AASes @@ -882,6 +1036,45 @@ public static async Task PerformConnectExtendedDialogue( row++; + // Pagination + helper.AddSmallLabelTo(g, row, 0, content: "Pagination:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + var g3 = helper.AddSmallGridTo(g, row, 1, 1, 4, new[] { "#", "*", "#", "*" }); + + helper.AddSmallLabelTo(g3, 0, 0, content: "Limit results:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g3, 0, 1, + margin: new AnyUiThickness(10, 0, 0, 0), + text: $"{record.PageLimit:D}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + minWidth: 80, maxWidth: 80, + horizontalAlignment: AnyUiHorizontalAlignment.Left), + (i) => { record.PageLimit = i; }); + + helper.AddSmallLabelTo(g3, 0, 2, content: "Skip results:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g3, 0, 3, + margin: new AnyUiThickness(10, 0, 0, 0), + text: $"{record.PageSkip:D}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + minWidth: 80, maxWidth: 80, + horizontalAlignment: AnyUiHorizontalAlignment.Left), + (i) => { record.PageSkip = i; }); + + row++; + // give back return g; }); diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index 251a8c778..4c4505603 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -274,9 +274,14 @@ await memStream.WriteAsync(buffer, 0, bytesRead, } } else + if (response.StatusCode == HttpStatusCode.NotFound) { - Log.Singleton.Error("DownloadFromSource Server gave: Operation not allowed!"); - throw new PackageContainerException($"Server operation not allowed!"); + // not found but also no error: intentionally do nothing + } + else + { + Log.Singleton.Error($"DownloadFromSource server gave status code {response.StatusCode}!"); + throw new PackageContainerException($"Unsuccessfull status code {response.StatusCode}"); } } } diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index a2a0accf5..c6f7e24fb 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -21,6 +21,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Globalization; using System.Linq; using System.Runtime.Serialization; +using System.Windows; using Aas = AasCore.Aas3_0; using Samm = AasCore.Samm2_2_0; @@ -67,6 +68,7 @@ public class VisualElementGeneric : INotifyPropertyChanged, IAnyUiSelectedItem private bool _isExpandedTouched = false; private bool _isSelected = false; public string TagString { get; set; } + public FontWeight TagWeight { get; set; } = FontWeights.Bold; private string _caption = ""; public string Caption @@ -569,16 +571,21 @@ public class VisualElementEnvironmentItem : VisualElementGeneric public enum ItemType { Env = 0, Shells, AllConceptDescriptions, Package, OrphanSubmodels, AllSubmodels, SupplFiles, + FetchPrev, FetchNext, CdValueReference, EmptySet, DummyNode }; public static string[] ItemTypeNames = new string[] { "Environment", "AdministrationShells", "ConceptDescriptions", "Package", "Orphan Submodels", - "All Submodels", "Supplemental files", "Value Aas.Reference", "Empty", "Dummy" }; + "All Submodels", "Supplemental files", + "Fetch previous", "Fetch next", + "Value Aas.Reference", "Empty", "Dummy" }; public static string[] ItemTypeFilter = new string[] { "Environment", "AdministrationShells", "ConceptDescriptions", "Package", "OrphanSubmodels", - "AllSubmodels", "SupplementalFiles", "Value.Aas.Reference", "Empty", "Dummy" }; + "AllSubmodels", "SupplementalFiles", + "Fetch previous", "Fetch next", + "Value.Aas.Reference", "Empty", "Dummy" }; public enum ConceptDescSortOrder { [EnumMember(Value = "ListIndex")] @@ -634,11 +641,26 @@ public VisualElementEnvironmentItem( } if (theItemType == ItemType.Package && thePackage != null) { - this.TagString = "\u25a2"; - if (thePackageSourceFn != null) - this.Info += "" + thePackageSourceFn; + if (thePackage is AdminShellPackageDynamicFetchEnv dynPack) + { + TagString = " \U0001f517 "; + TagWeight = FontWeights.Normal; + Info += "" + dynPack.Filename; + } else - this.Info += "" + thePackage.Filename; + { + TagString = "Env"; + TagWeight = FontWeights.Normal; + if (thePackageSourceFn != null) + this.Info += "" + thePackageSourceFn; + else + this.Info += "" + thePackage.Filename; + } + } + if (theItemType == ItemType.FetchNext) + { + TagString = " \u21ca "; + TagWeight = FontWeights.Normal; } RestoreFromCache(); } @@ -739,8 +761,12 @@ public override void RefreshFromMainData() { if (theAas != null) { + var td = ""; + if (GetTaintedTime() != null) + td = "\u273d "; + var ci = theAas.ToCaptionInfo(); - this.Caption = ci.Item1; + this.Caption = td + ci.Item1; this.Info = ci.Item2; var asset = theAas.AssetInformation; if (asset != null) @@ -1682,6 +1708,8 @@ public override void RefreshFromMainData() public class ListOfVisualElement : ObservableCollection { + public static int ConstThresholdExpandAas = 5; + public bool OptionLazyLoadingFirst = false; public int OptionExpandMode = 0; @@ -1985,7 +2013,8 @@ private VisualElementSubmodelRef GenerateVisuElemForVisualElementSubmodelRef( private VisualElementAdminShell GenerateVisuElemForAAS( Aas.IAssetAdministrationShell aas, TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package = null, - bool editMode = false) + bool editMode = false, + bool aasIsExpanded = true) { // trivial if (aas == null) @@ -2001,6 +2030,7 @@ private VisualElementAdminShell GenerateVisuElemForAAS( { // item var tiAsset = new VisualElementAsset(tiAas, cache, env, aas, asset); + tiAas.IsExpanded = aasIsExpanded; tiAas.Members.Add(tiAsset); } @@ -2019,12 +2049,16 @@ private VisualElementAdminShell GenerateVisuElemForAAS( { var si = smsi.GetSideInfo(ndx); - // add stub - var tiSmSi = new VisualElementSubmodelStub(tiAas, cache, package, si); - tiAas.Members.Add(tiSmSi); + if (si.IsStub) + { + + // add stub + var tiSmSi = new VisualElementSubmodelStub(tiAas, cache, package, si); + tiAas.Members.Add(tiSmSi); - // not further here!! - continue; + // not further here!! + continue; + } } } @@ -2389,25 +2423,48 @@ public void AddVisualElementsFromShellEnv( } // over all Admin shells - if (env != null) + if (env != null && env.AssetAdministrationShellCount() > 0) { - foreach (var aas in env.AllAssetAdministrationShells()) + for (int aasi=0; aasi< env.AssetAdministrationShellCount(); aasi++) { + // get info(s) + var aas = env.AssetAdministrationShells[aasi]; + var si = (env.AssetAdministrationShells is + OnDemandListIdentifiable dynAass) + ? dynAass.GetSideInfo(aasi) : null; + // item - var tiAas = GenerateVisuElemForAAS(aas, cache, env, package, editMode); + var tiAas = GenerateVisuElemForAAS( + aas, cache, env, package, editMode, + aasIsExpanded: env.AssetAdministrationShellCount() <= ConstThresholdExpandAas); - // add item - if (tiAas != null) + // lambda to add + Action lambdaAdd = (ve) => { - if (editMode) - { - tiAas.Parent = tiShells; - tiShells.Members.Add(tiAas); - } - else + if (ve != null) { - res.Add(tiAas); + if (editMode) + { + ve.Parent = tiShells; + tiShells.Members.Add(ve); + } + else + { + res.Add(ve); + } } + }; + + // add item + lambdaAdd(tiAas); + + // display elements after? + if (si?.ShowCursorBelow == true) + { + lambdaAdd(new VisualElementEnvironmentItem( + null /* Parent */, cache, package, env, + VisualElementEnvironmentItem.ItemType.FetchNext, + mainDataObject: null)); } } } @@ -2431,7 +2488,7 @@ public void AddVisualElementsFromShellEnv( if (env.Submodels[smi] == null && env.Submodels is OnDemandList smsi) { var si = smsi.GetSideInfo(smi); - if (si != null) + if (si != null && si.IsStub) { // add stub var tiSmSi = new VisualElementSubmodelStub(tiAllSubmodels, cache, package, si); @@ -2646,6 +2703,14 @@ public IEnumerable FindAllVisualElementOf(Predicate p) yield return te; } + public bool IsAnyTaintedIdentifiable() + { + foreach (var ve in this.FindAllVisualElementTopToIdentifiable()) + if (ve is ITaintableIdentifiable tain && tain.GetTaintedTime() != null) + return true; + return false; + } + public bool ContainsDeep(VisualElementGeneric ve) { // ReSharper disable UnusedVariable diff --git a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml index 79fc81b54..35914b367 100644 --- a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml +++ b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml @@ -55,7 +55,7 @@ CornerRadius="0" Width="30" Height="22"> diff --git a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs index a983a1f69..b0d717b07 100644 --- a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs +++ b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs @@ -1214,6 +1214,11 @@ public void TrySelectMainDataObjects(IEnumerable mainObjects, bool preve FireSelectedItem(); } + public bool IsAnyTaintedIdentifiable() + { + return displayedTreeViewLines?.IsAnyTaintedIdentifiable() == true; + } + } } diff --git a/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs b/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs index 10878af05..c09e64cfc 100644 --- a/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs +++ b/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs @@ -271,6 +271,7 @@ public DisplayRenderHints DisplayOrEditVisualAasxElement( bool editMode, bool hintMode = false, bool showIriMode = false, bool checkSmt = false, VisualElementEnvironmentItem.ConceptDescSortOrder? cdSortOrder = null, IFlyoutProvider flyoutProvider = null, + IMainWindow mainWindow = null, IPushApplicationEvent appEventProvider = null, DispEditHighlight.HighlightFieldInfo hightlightField = null, AasxMenu superMenu = null) @@ -375,7 +376,8 @@ public DisplayRenderHints DisplayOrEditVisualAasxElement( // try to delegate to common routine var common = _helper.DisplayOrEditCommonEntity( - packages, stack, superMenu, editMode, hintMode, checkSmt, cdSortOrder, entity); + packages, stack, superMenu, editMode, hintMode, checkSmt, cdSortOrder, entity, + mainWindow: mainWindow); if (common) { diff --git a/src/BlazorExplorer/BlazorVisualElements.cs b/src/BlazorExplorer/BlazorVisualElements.cs index ec0735464..6ff7650c3 100644 --- a/src/BlazorExplorer/BlazorVisualElements.cs +++ b/src/BlazorExplorer/BlazorVisualElements.cs @@ -465,5 +465,11 @@ public void NotifyTreeSelectionChanged(VisualElementGeneric ve, BlazorInput.Keyb // dead-csharp on } } + + public bool IsAnyTaintedIdentifiable() + { + return TreeItems?.IsAnyTaintedIdentifiable() == true; + } + } } diff --git a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs index 6b1d4e3f7..144a14e3e 100644 --- a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs +++ b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs @@ -132,7 +132,9 @@ public void UiLoadPackageWithNew( bool doNotNavigateAfterLoaded = false, PackageContainerBase takeOverContainer = null, string storeFnToLRU = null, - bool indexItems = false) + bool indexItems = false, + bool preserveEditMode = false, + bool? nextEditMode = null) { // access if (packItem == null) @@ -221,7 +223,12 @@ public void UiLoadPackageWithNew( Log.Singleton.Info("AASX {0} loaded.", info); } - public void RestartUIafterNewPackage(bool onlyAuxiliary = false) + public bool CheckIsAnyTaintedIdentifiableInMain() + { + return DisplayElements.IsAnyTaintedIdentifiable(); + } + + public void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null) { if (onlyAuxiliary) { diff --git a/src/BlazorExplorer/Data/BlazorSession.cs b/src/BlazorExplorer/Data/BlazorSession.cs index 428c6bd66..60653b76a 100644 --- a/src/BlazorExplorer/Data/BlazorSession.cs +++ b/src/BlazorExplorer/Data/BlazorSession.cs @@ -496,7 +496,8 @@ public bool PrepareDisplayDataAndElementPanel( elementPanel, superMenu, EditMode, HintMode, CheckSmtMode, tiCds?.CdSortOrder ?? VisualElementEnvironmentItem.ConceptDescSortOrder.None, - DisplayElements.SelectedItem); + DisplayElements.SelectedItem, + mainWindow: null); // TODO: fix mainWindow if (common) { From b1ae05fc7a81f2ebae4a98ff93f787570a6c74ec Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sat, 5 Oct 2024 19:05:31 +0200 Subject: [PATCH 09/99] * need to refactor PageOffset --- src/AasxPackageExplorer/MainWindow.xaml.cs | 34 ++- .../DispEditHelperEntities.cs | 280 +++++++++++------- .../MainWindowAnyUiDialogs.cs | 100 ++++--- .../PackageCentral/AasOnDemandEnvironment.cs | 1 + .../PackageContainerHttpRepoSubset.cs | 42 ++- src/AasxPackageLogic/VisualAasxElements.cs | 16 +- 6 files changed, 310 insertions(+), 163 deletions(-) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 20b493e79..21aeaf22b 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -24,6 +24,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using NPOI.HPSF; using Org.BouncyCastle.Asn1.X509; using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -2929,7 +2930,7 @@ private void DisplayElements_SelectedItemChanged(object sender, EventArgs e) RedrawElementView(); } - private void DisplayElements_MouseDoubleClick(object sender, MouseButtonEventArgs e) + private async void DisplayElements_MouseDoubleClick(object sender, MouseButtonEventArgs e) { // we're assuming, that SelectedItem point to the right business object var si = DisplayElements.SelectedItem; @@ -2937,13 +2938,40 @@ private void DisplayElements_MouseDoubleClick(object sender, MouseButtonEventArg return; // act depending on selectedItem - if (si is VisualElementEnvironmentItem siei && (siei.theItemType == VisualElementEnvironmentItem.ItemType.FetchPrev || siei.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext)) { // want to refetch elements - ; + // check all pre-requisites + if (!(siei.thePackage is AdminShellPackageDynamicFetchEnv dynPack + && dynPack.GetContext() is PackageContainerHttpRepoSubsetFetchContext fetchContext + && fetchContext.Record != null)) + { + Log.Singleton.Error("Fetch next within dynamic environment: " + + "Not enough data to provide dynamic fetch operations."); + return; + } + + // at the end? + if (siei.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext + && fetchContext.Cursor?.HasContent() != true) + { + Log.Singleton.Error("No further fetch operation available " + + "(at the end of the selected subset of elements?)."); + return; + } + + // refer to (static) function + var res = await DispEditHelperEntities.ExecuteUiForFetchOfElements( + PackageCentral, DisplayContext, new AasxMenuActionTicket(), this /* MainWindow */, fetchContext, + preserveEditMode: true, + doEditNewRecord: false, + doCheckTainted: true, + doFetchGoNext: true, + doFetchExec: true); + + // success will trigger redraw independently, therefore always do nothing } else if (si is VisualElementSubmodelElement) diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 9320f974f..0cf36fb43 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -29,6 +29,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxPackageExplorer; using System.Threading.Tasks; using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; +using VDS.Common.Filters; namespace AasxPackageLogic { @@ -1166,64 +1167,16 @@ public void DisplayOrEditAasEntityAasEnv( "Fetch the next set of elements."), ticketActionAsync: async (buttonNdx, ticket) => { - //await Task.Yield(); - - //if (buttonNdx == 0) - //{ - // // check if something is tainted - // if (mainWindow?.CheckIsAnyTaintedIdentifiableInMain() == true) - // { - // if (AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( - // "There are unsafed data changes in Identifiables. A fetch of elements " + - // "might result in data loss.", - // "Proceed with fetch?", - // AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) - // return new AnyUiLambdaActionNone(); - // } - - // // modify (!) record data to do no skip anymore, using cursor data - // fetchContext.Record.PageSkip = 0; - // var location = PackageContainerHttpRepoSubset.BuildLocationFrom( - // fetchContext.Record, fetchContext.Cursor); - // if (location == null) - // { - // MainWindowLogic.LogErrorToTicketStatic(ticket, - // new InvalidDataException(), - // "Error building location from next fetch selection. Aborting."); - // return new AnyUiLambdaActionNone(); - // } - - // // more details into container options - // var containerOptions = new PackageContainerHttpRepoSubset. - // PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), - // fetchContext.Record); - - // // load - // Log.Singleton.Info($"For refining extended connect, loading " + - // $"from {location} into container"); - - // var container = await PackageContainerFactory.GuessAndCreateForAsync( - // packages, - // location, - // location, - // overrideLoadResident: true, - // containerOptions: containerOptions, - // runtimeOptions: packages.CentralRuntimeOptions); - - // if (container == null) - // Log.Singleton.Error($"Failed to load from {location}"); - // else - // mainWindow.UiLoadPackageWithNew(packages.MainItem, - // takeOverContainer: container, onlyAuxiliary: false, indexItems: true, - // storeFnToLRU: location, - // nextEditMode: editMode); - - // Log.Singleton.Info($"Successfully loaded {location}"); - //} - //return new AnyUiLambdaActionNone(); - - return await ExecuteUiForFetchOfElements - ("fetch-next", packages, context, editMode, ticket, mainWindow, fetchContext); + var res = await ExecuteUiForFetchOfElements( + packages, context, ticket, mainWindow, fetchContext, + preserveEditMode: true, + doEditNewRecord: false, + doCheckTainted: true, + doFetchGoNext: true, + doFetchExec: true); + + // success will trigger redraw independently, therefore always return none + return new AnyUiLambdaActionNone(); }); } else @@ -1272,6 +1225,36 @@ public void DisplayOrEditAasEntityAasEnv( "endpoints such as registries and repositories.", severityLevel: HintCheck.Severity.Notice)); + this.AddGroup(stack, "Dynamic fetch environment", this.levelColors.SubSection); + + // more infos? + if (dynPack.GetContext() is PackageContainerHttpRepoSubsetFetchContext fetchContext + && fetchContext.Record != null) + { + var record = fetchContext.Record; + + AddKeyValue(stack, key: "BaseType", repo: null, + value: record.GetBaseTypStr().ToUpper()); + + AddKeyValue(stack, key: "BaseAddress", repo: null, + value: record.BaseAddress); + + AddKeyValue(stack, key: "Operation", repo: null, + value: record.GetFetchOperationStr()); + + if (record.GetAllAas) + { + AddKeyValue(stack, key: "Page limit", repo: null, + value: "" + record.PageLimit); + + AddKeyValue(stack, key: "Page skip", repo: null, + value: "" + record.PageSkip); + + AddKeyValue(stack, key: "Page offset", repo: null, + value: "" + fetchContext.PageOffset + " (counted)"); + } + } + AddActionPanel(stack, "Actions:", repo: repo, superMenu: superMenu, @@ -1289,49 +1272,69 @@ public void DisplayOrEditAasEntityAasEnv( as ConnectExtendedRecord) ?? new PackageContainerHttpRepoSubset.ConnectExtendedRecord(); - var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( - ticket, plusDialogs, - "Connect AAS repositories and registries", - record); + //record.PageSkip = 0; - if (!uiRes) - return new AnyUiLambdaActionNone(); ; + //var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( + // ticket, plusDialogs, + // "Connect AAS repositories and registries", + // record); - var location = PackageContainerHttpRepoSubset.BuildLocationFrom(record); - if (location == null) + //if (!uiRes) + // return new AnyUiLambdaActionNone(); + + // ok, prepare new fetch context (no continuiation) + var fetchContext = new PackageContainerHttpRepoSubsetFetchContext() { - MainWindowLogic.LogErrorToTicketStatic(ticket, - new InvalidDataException(), - "Error building location from query selection. Aborting."); - return new AnyUiLambdaActionNone(); - } + Record = record + }; - // more details into container options - var containerOptions = new PackageContainerHttpRepoSubset. - PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), - record); - - // load - Log.Singleton.Info($"For refining extended connect, loading " + - $"from {location} into container"); - - var container = await PackageContainerFactory.GuessAndCreateForAsync( - packages, - location, - location, - overrideLoadResident: true, - containerOptions: containerOptions, - runtimeOptions: packages.CentralRuntimeOptions); - - if (container == null) - Log.Singleton.Error($"Failed to load from {location}"); - else - mainWindow.UiLoadPackageWithNew(packages.MainItem, - takeOverContainer: container, onlyAuxiliary: false, indexItems: true, - storeFnToLRU: location, - nextEditMode: editMode); + // refer to (static) function + var res = await ExecuteUiForFetchOfElements( + packages, context, ticket, mainWindow, fetchContext, + preserveEditMode: true, + doEditNewRecord: true, + doCheckTainted: true, + doFetchGoNext: false, + doFetchExec: true); + + // success will trigger redraw independently, therefore always return none + return new AnyUiLambdaActionNone(); - Log.Singleton.Info($"Successfully loaded {location}"); + //var location = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + //if (location == null) + //{ + // MainWindowLogic.LogErrorToTicketStatic(ticket, + // new InvalidDataException(), + // "Error building location from query selection. Aborting."); + // return new AnyUiLambdaActionNone(); + //} + + //// more details into container options + //var containerOptions = new PackageContainerHttpRepoSubset. + // PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + // record); + + //// load + //Log.Singleton.Info($"For refining extended connect, loading " + + // $"from {location} into container"); + + //var container = await PackageContainerFactory.GuessAndCreateForAsync( + // packages, + // location, + // location, + // overrideLoadResident: true, + // containerOptions: containerOptions, + // runtimeOptions: packages.CentralRuntimeOptions); + + //if (container == null) + // Log.Singleton.Error($"Failed to load from {location}"); + //else + // mainWindow.UiLoadPackageWithNew(packages.MainItem, + // takeOverContainer: container, onlyAuxiliary: false, indexItems: true, + // storeFnToLRU: location, + // nextEditMode: editMode); + + //Log.Singleton.Info($"Successfully loaded {location}"); } return new AnyUiLambdaActionNone(); }); @@ -1416,18 +1419,29 @@ public void DisplayOrEditAasEntitySupplementaryFile( // // - public static async Task ExecuteUiForFetchOfElements( - string mode, + /// + /// Fetch helper. Different do... flags are to be set! + /// + /// True, when a new fetch has been executed successfully + public static async Task ExecuteUiForFetchOfElements( PackageCentral.PackageCentral packages, AnyUiContextBase displayContext, - bool editMode, AasxMenuActionTicket ticket, IMainWindow mainWindow, - PackageContainerHttpRepoSubsetFetchContext fetchContext) + PackageContainerHttpRepoSubsetFetchContext fetchContext, + bool preserveEditMode = true, + bool doCheckTainted = false, + bool doEditNewRecord = false, + bool doFetchGoNext = false, + bool doFetchExec = false) { await Task.Yield(); - - if (mode == "fetch-next") + + // fetchContext is required!! + if (fetchContext == null) + return false; + + if (doCheckTainted) { // check if something is tainted if (mainWindow?.CheckIsAnyTaintedIdentifiableInMain() == true) @@ -1437,19 +1451,47 @@ public static async Task ExecuteUiForFetchOfElements( "might result in data loss.", "Proceed with fetch?", AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) - return new AnyUiLambdaActionNone(); + return false; } + } + + if (doEditNewRecord) + { + var record = fetchContext.Record ?? new PackageContainerHttpRepoSubset.ConnectExtendedRecord(); + var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( + ticket, displayContext, + "Connect AAS repositories and registries", + record); + + if (!uiRes) + return false; + + // modify fetch context to be "fresh" + fetchContext.PageOffset = 0; + fetchContext.Cursor = null; + fetchContext.Record = record; + } + + if (doFetchGoNext) + { // modify (!) record data to do no skip anymore, using cursor data + fetchContext.PageOffset += (fetchContext.Record.PageLimit + fetchContext.Record.PageSkip); fetchContext.Record.PageSkip = 0; + } + + if (doFetchExec) + { + // build location var location = PackageContainerHttpRepoSubset.BuildLocationFrom( - fetchContext.Record, fetchContext.Cursor); + fetchContext.Record, fetchContext.Cursor); + if (location == null) { MainWindowLogic.LogErrorToTicketStatic(ticket, new InvalidDataException(), "Error building location from next fetch selection. Aborting."); - return new AnyUiLambdaActionNone(); + return false; } // more details into container options @@ -1470,17 +1512,31 @@ public static async Task ExecuteUiForFetchOfElements( runtimeOptions: packages.CentralRuntimeOptions); if (container == null) + { Log.Singleton.Error($"Failed to load from {location}"); - else - mainWindow.UiLoadPackageWithNew(packages.MainItem, - takeOverContainer: container, onlyAuxiliary: false, indexItems: true, - storeFnToLRU: location, - nextEditMode: editMode); + return false; + } + + // manual update of page offset + if ((container.Env as AdminShellPackageDynamicFetchEnv)?.GetContext() + is PackageContainerHttpRepoSubsetFetchContext fc2) + { + fc2.PageOffset = fetchContext.PageOffset; + } + + // display + mainWindow.UiLoadPackageWithNew(packages.MainItem, + takeOverContainer: container, onlyAuxiliary: false, indexItems: true, + storeFnToLRU: location, + preserveEditMode: preserveEditMode); + + Log.Singleton.Info($"Successfully loaded {location}"); - Log.Singleton.Info($"Successfully loaded {location}"); + // okay + return true; } - return new AnyUiLambdaActionNone(); + return false; } // diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index c5f9cdaec..350464818 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -509,49 +509,65 @@ await val.PerformDialogue(ticket, DisplayContext, //do try { - var record = new ConnectExtendedRecord(); - - var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( - ticket, DisplayContext, - "Connect AAS repositories and registries", - record); - - if (!uiRes) - return; - - var location = PackageContainerHttpRepoSubset.BuildLocationFrom(record); - if (location == null) + var fetchContext = new PackageContainerHttpRepoSubsetFetchContext() { - LogErrorToTicket(ticket, "Error building location from query selection. Aborting."); - return; - } - - // more details into container options - var containerOptions = new PackageContainerHttpRepoSubset. - PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), - record); - - // load - Log.Singleton.Info($"For extended connect, loading " + - $"from {location} into container"); - - var container = await PackageContainerFactory.GuessAndCreateForAsync( - PackageCentral, - location, - location, - overrideLoadResident: true, - containerOptions: containerOptions, - runtimeOptions: PackageCentral.CentralRuntimeOptions); - - if (container == null) - Log.Singleton.Error($"Failed to load from {location}"); - else - MainWindow.UiLoadPackageWithNew(PackageCentral.MainItem, - takeOverContainer: container, onlyAuxiliary: false, indexItems: true, - storeFnToLRU: location, - preserveEditMode: true) ; - - Log.Singleton.Info($"Successfully loaded {location}"); + Record = new ConnectExtendedRecord() + }; + + // refer to (static) function + var res = await DispEditHelperEntities.ExecuteUiForFetchOfElements( + PackageCentral, DisplayContext, ticket, MainWindow, fetchContext, + preserveEditMode: true, + doEditNewRecord: true, + doCheckTainted: true, + doFetchGoNext: false, + doFetchExec: true); + + // success will trigger redraw independently, therefore always do nothing + + //var record = new ConnectExtendedRecord(); + + //var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( + // ticket, DisplayContext, + // "Connect AAS repositories and registries", + // record); + + //if (!uiRes) + // return; + + //var location = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + //if (location == null) + //{ + // LogErrorToTicket(ticket, "Error building location from query selection. Aborting."); + // return; + //} + + //// more details into container options + //var containerOptions = new PackageContainerHttpRepoSubset. + // PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + // record); + + //// load + //Log.Singleton.Info($"For extended connect, loading " + + // $"from {location} into container"); + + //var container = await PackageContainerFactory.GuessAndCreateForAsync( + // PackageCentral, + // location, + // location, + // overrideLoadResident: true, + // containerOptions: containerOptions, + // runtimeOptions: PackageCentral.CentralRuntimeOptions); + + //if (container == null) + // Log.Singleton.Error($"Failed to load from {location}"); + //else + // MainWindow.UiLoadPackageWithNew(PackageCentral.MainItem, + // takeOverContainer: container, onlyAuxiliary: false, indexItems: true, + // storeFnToLRU: location, + // preserveEditMode: true) ; + + //Log.Singleton.Info($"Successfully loaded {location}"); } catch (Exception ex) { diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index e73da3b3e..eaa94cee7 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -43,6 +43,7 @@ public class AasIdentifiableSideInfo : OnDemandSideInfoBase public string Id = ""; public string IdShort = ""; + public bool ShowCursorAbove = false; public bool ShowCursorBelow = false; } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index c47ab2f9d..da41f40dc 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -39,7 +39,17 @@ namespace AasxPackageLogic.PackageCentral public class PackageContainerHttpRepoSubsetFetchContext : AdminShellPackageDynamicFetchContextBase { public PackageContainerHttpRepoSubset.ConnectExtendedRecord Record; + + /// + /// Cursor, as provided by the server. + /// public string Cursor; + + /// + /// This offset in elements is thought-out by this client by "counting". It does NOT come form + /// the server! + /// + public int PageOffset; } /// @@ -384,10 +394,12 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( continue; } - // on last child, attach side info for fetch next cursor - var lastChildAndMore = n2 == resChilds.Last() && record.PageLimit > 0; - var si = (!lastChildAndMore) ? null - : new AasIdentifiableSideInfo() + // on last child, attach side info for fetch prev/ next cursor + AasIdentifiableSideInfo si = null; + // if (n2 == resChilds.First() && record.Pa) + + if (n2 == resChilds.Last() && record.PageLimit > 0) + si = new AasIdentifiableSideInfo() { IsStub = false, ShowCursorBelow = true @@ -564,7 +576,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( EnvDynPack?.SetContext(new PackageContainerHttpRepoSubsetFetchContext() { Record = record, - Cursor = cursor + Cursor = cursor, + PageOffset = 0 // by definition }); } @@ -643,6 +656,25 @@ public void SetQueryChoices(int choice) GetSingleCD = (choice == 4); ExecuteQuery = (choice == 5); } + + public string GetBaseTypStr() + { + return AdminShellUtil.MapIntToStringArray((int)BaseType, + "Unknown", ConnectExtendedRecord.BaseTypeEnumNames); + } + + public string GetFetchOperationStr() + { + var res = "Unknown"; + + if (GetAllAas) res = "GetAllAssetAdministrationShells"; + if (GetSingleAas) res = "GetAssetAdministrationShellById"; + if (GetSingleSubmodel) res = "GetSubmodelById"; + if (GetSingleCD) res = "GetConceptDescriptionById"; + if (ExecuteQuery) res = "ExecuteQuery"; + + return res; + } } public class PackageContainerHttpRepoSubsetOptions : PackageContainerOptionsBase diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index c6f7e24fb..1d68f2f40 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -22,6 +22,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Linq; using System.Runtime.Serialization; using System.Windows; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; using Aas = AasCore.Aas3_0; using Samm = AasCore.Samm2_2_0; @@ -645,7 +646,20 @@ public VisualElementEnvironmentItem( { TagString = " \U0001f517 "; TagWeight = FontWeights.Normal; - Info += "" + dynPack.Filename; + + // add knowledge from record + if (dynPack.GetContext() is PackageContainerHttpRepoSubsetFetchContext fetchContext + && fetchContext.Record != null) + { + // show base of Repo/ Registry + Caption = fetchContext.Record.GetBaseTypStr().ToUpper(); + Info += fetchContext.Record.BaseAddress; + } + else + { + // normal + Info += "" + dynPack.Filename; + } } else { From 8cc656ccfd428984daf974f90da76a7470db5779 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 6 Oct 2024 12:29:55 +0200 Subject: [PATCH 10/99] * start try refactor for virtual element of Submodel having child --- src/AasxPackageExplorer/MainWindow.xaml.cs | 6 +- .../options-debug.MIHO.json | 1 + .../DispEditHelperEntities.cs | 132 +++++++++++++----- src/AasxPackageLogic/Options.cs | 7 + .../PackageCentral/AasOnDemandEnvironment.cs | 7 + .../PackageCentral/PackageContainerFactory.cs | 1 - .../PackageContainerHttpRepoSubset.cs | 123 ++++++++++++---- src/AasxPackageLogic/VisualAasxElements.cs | 57 ++++++-- 8 files changed, 258 insertions(+), 76 deletions(-) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 21aeaf22b..ef474b26d 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -355,7 +355,8 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() return AnyUiMessageBoxResult.Cancel; else return ucic.MessageBoxShow(content, text, title, buttons); - } + }, + AllowFakeResponses = Options.Curr.AllowFakeResponses }; return ro; } @@ -2968,7 +2969,8 @@ private async void DisplayElements_MouseDoubleClick(object sender, MouseButtonEv preserveEditMode: true, doEditNewRecord: false, doCheckTainted: true, - doFetchGoNext: true, + doFetchGoPrev: siei.theItemType == VisualElementEnvironmentItem.ItemType.FetchPrev, + doFetchGoNext: siei.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext, doFetchExec: true); // success will trigger redraw independently, therefore always do nothing diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index c1fa7d883..9f471745f 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -103,6 +103,7 @@ "BackupDir": ".\\backup", "BackupFiles": 10, "MaxParallelOps": 20, + "AllowFakeResponses": true, "RestServerHost": "localhost", "RestServerPort": "1111", "WriteDefaultOptionsFN": null /* "options-written.json" */, diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 0cf36fb43..3b839b904 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -1124,7 +1124,8 @@ public void DisplayOrEditAasEntityAasEnv( }); stack.Children.Add(g); } - else if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext) + else if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext + || ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchPrev) { // check all pre-requisites if (!(context is AnyUiContextPlusDialogs plusDialogs @@ -1140,44 +1141,96 @@ public void DisplayOrEditAasEntityAasEnv( return; } - // at the end? - if (fetchContext.Cursor?.HasContent() != true) + // at the beginning already + if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchPrev + && fetchContext.Record.PageOffset == 0) { AddHintBubble(stack, hintMode, new HintCheck( () => true, "No further fetch operation available " + - "(at the end of the selected subset of elements?).", + "(at the beginning of the selected subset of elements?).", severityLevel: HintCheck.Severity.Notice)); return; } - // go ahead - AddHintBubble(stack, hintMode, new HintCheck( + // at the end? + if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext + && fetchContext.Cursor?.HasContent() != true) + { + AddHintBubble(stack, hintMode, new HintCheck( () => true, - "The entities in this structure were fetched dynamically from " + - "endpoints such as registries and repositories. This fetch could " + - "be advanced to the next set of elements.", + "No further fetch operation available " + + "(at the end of the selected subset of elements?).", severityLevel: HintCheck.Severity.Notice)); + return; + } - AddActionPanel(stack, "Actions:", - repo: repo, - superMenu: superMenu, - ticketMenu: new AasxMenu() - .AddAction("fetch-next", "Fetch next", - "Fetch the next set of elements."), - ticketActionAsync: async (buttonNdx, ticket) => - { - var res = await ExecuteUiForFetchOfElements( - packages, context, ticket, mainWindow, fetchContext, - preserveEditMode: true, - doEditNewRecord: false, - doCheckTainted: true, - doFetchGoNext: true, - doFetchExec: true); - - // success will trigger redraw independently, therefore always return none - return new AnyUiLambdaActionNone(); + // go ahead + if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchPrev) + AddHintBubble(stack, hintMode, new[] { + new HintCheck( + () => true, + "The entities in this structure were fetched dynamically from " + + "endpoints such as registries and repositories. This fetch could " + + "be modified to load elements prior to the displayed set of elements. ", + severityLevel: HintCheck.Severity.Notice), + new HintCheck( + () => true, + "Note: This operation causes many elements to be reloaded and skipped, " + + "therefore might be a long-lasting operation.", + severityLevel: HintCheck.Severity.Notice) }); + + if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext) + AddHintBubble(stack, hintMode, new HintCheck( + () => true, + "The entities in this structure were fetched dynamically from " + + "endpoints such as registries and repositories. This fetch could " + + "be advanced to the next set of elements.", + severityLevel: HintCheck.Severity.Notice)); + + if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchPrev) + AddActionPanel(stack, "Actions:", + repo: repo, + superMenu: superMenu, + ticketMenu: new AasxMenu() + .AddAction("fetch-prev", "Fetch prev", + "Fetch the previous set of elements."), + ticketActionAsync: async (buttonNdx, ticket) => + { + var res = await ExecuteUiForFetchOfElements( + packages, context, ticket, mainWindow, fetchContext, + preserveEditMode: true, + doEditNewRecord: false, + doCheckTainted: true, + doFetchGoPrev: true, + doFetchExec: true); + + // success will trigger redraw independently, therefore always return none + return new AnyUiLambdaActionNone(); + }); + + + if (ve.theItemType == VisualElementEnvironmentItem.ItemType.FetchNext) + AddActionPanel(stack, "Actions:", + repo: repo, + superMenu: superMenu, + ticketMenu: new AasxMenu() + .AddAction("fetch-next", "Fetch next", + "Fetch the next set of elements."), + ticketActionAsync: async (buttonNdx, ticket) => + { + var res = await ExecuteUiForFetchOfElements( + packages, context, ticket, mainWindow, fetchContext, + preserveEditMode: true, + doEditNewRecord: false, + doCheckTainted: true, + doFetchGoNext: true, + doFetchExec: true); + + // success will trigger redraw independently, therefore always return none + return new AnyUiLambdaActionNone(); + }); } else { @@ -1242,7 +1295,7 @@ public void DisplayOrEditAasEntityAasEnv( AddKeyValue(stack, key: "Operation", repo: null, value: record.GetFetchOperationStr()); - if (record.GetAllAas) + if (record.GetAllAas || record.GetAllSubmodel) { AddKeyValue(stack, key: "Page limit", repo: null, value: "" + record.PageLimit); @@ -1251,7 +1304,7 @@ public void DisplayOrEditAasEntityAasEnv( value: "" + record.PageSkip); AddKeyValue(stack, key: "Page offset", repo: null, - value: "" + fetchContext.PageOffset + " (counted)"); + value: "" + record.PageOffset + " (counted)"); } } @@ -1432,6 +1485,7 @@ public static async Task ExecuteUiForFetchOfElements( bool preserveEditMode = true, bool doCheckTainted = false, bool doEditNewRecord = false, + bool doFetchGoPrev = false, bool doFetchGoNext = false, bool doFetchExec = false) { @@ -1468,15 +1522,24 @@ public static async Task ExecuteUiForFetchOfElements( return false; // modify fetch context to be "fresh" - fetchContext.PageOffset = 0; fetchContext.Cursor = null; fetchContext.Record = record; } + if (doFetchGoPrev) + { + // provide no cursor, therefore fetch from very beginning, skip elements + fetchContext.Cursor = null; + fetchContext.Record.PageSkip = Math.Max(0, fetchContext.Record.PageOffset - fetchContext.Record.PageLimit); + fetchContext.Record.PageOffset -= fetchContext.Record.PageLimit; + fetchContext.Record.PageOffset = Math.Max(0, fetchContext.Record.PageOffset); + } + if (doFetchGoNext) { // modify (!) record data to do no skip anymore, using cursor data - fetchContext.PageOffset += (fetchContext.Record.PageLimit + fetchContext.Record.PageSkip); + fetchContext.Record.PageOffset += (fetchContext.Record.PageLimit + fetchContext.Record.PageSkip); + fetchContext.Record.PageOffset = Math.Max(0, fetchContext.Record.PageOffset); fetchContext.Record.PageSkip = 0; } @@ -1517,13 +1580,6 @@ public static async Task ExecuteUiForFetchOfElements( return false; } - // manual update of page offset - if ((container.Env as AdminShellPackageDynamicFetchEnv)?.GetContext() - is PackageContainerHttpRepoSubsetFetchContext fc2) - { - fc2.PageOffset = fetchContext.PageOffset; - } - // display mainWindow.UiLoadPackageWithNew(packages.MainItem, takeOverContainer: container, onlyAuxiliary: false, indexItems: true, diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index c58fae35b..93e43e08e 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -444,6 +444,13 @@ public class OptionsInformation [OptionDescription(Description = "Maximum parallel operations, such as HTTP downloads.")] public int MaxParallelOps = 20; + [OptionDescription(Description = + "If true, will answer the HTTP GET requests of selected ressources by internal, fixed responses " + + "(see PackageContainerFakeAnswers.json). This allows quick testing of repository functions without " + + "internet connection (shoutout to Deutsche Bahn).", + Cmd = "-allow-fake-responses")] + public bool AllowFakeResponses = true; + [OptionDescription(Description = "If set, load and store AASX files via temporary package to " + "avoid corruptions. RECOMMENDED!", Cmd = "-indirect-load-save")] diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index eaa94cee7..6b55462de 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -43,7 +43,14 @@ public class AasIdentifiableSideInfo : OnDemandSideInfoBase public string Id = ""; public string IdShort = ""; + /// + /// In the tree of virtual elements, place a visual element below this entity + /// public bool ShowCursorAbove = false; + + /// + /// In the tree of virtual elements, place a visual element below this entity + /// public bool ShowCursorBelow = false; } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs index 4d32910b7..0a1e04d4d 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs @@ -220,7 +220,6 @@ public static async Task GuessAndCreateForAsync( new ConnectExtendedRecord()); // prepare runtime options - runtimeOptions.AllowFakeResponses = false; var cnt = await PackageContainerHttpRepoSubset.CreateAndLoadAsync( packageCentral, location, fullItemLocation, overrideLoadResident, takeOver: takeOver, diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index da41f40dc..d0484cdfb 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -45,11 +45,6 @@ public class PackageContainerHttpRepoSubsetFetchContext : AdminShellPackageDynam /// public string Cursor; - /// - /// This offset in elements is thought-out by this client by "counting". It does NOT come form - /// the server! - /// - public int PageOffset; } /// @@ -162,6 +157,13 @@ public static bool IsValidUriForSingleAAS(string location) return m.Success; } + public static bool IsValidUriForAllSubmodel(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels(|/|/?\?(.*))$", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + public static bool IsValidUriForSingleSubmodel(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels/(.{1,99})$", @@ -192,6 +194,7 @@ public static bool IsValidUriAnyMatch(string location) { return IsValidUriForAllAAS(location) || IsValidUriForSingleAAS(location) + || IsValidUriForAllSubmodel(location) || IsValidUriForSingleSubmodel(location) || IsValidUriForSingleCD(location) || IsValidUriForQuery(location); @@ -287,6 +290,22 @@ public static Uri BuildUriForAasThumbnail(Uri baseUri, string id, bool encryptId return CombineUri(baseUri, $"shells/{smidenc}/asset-information/thumbnail"); } + public static Uri BuildUriForAllSubmodel(Uri baseUri, int pageLimit = 100, string cursor = null) + { + // for more info: see BuildUriForAllAAS + // access + if (baseUri == null) + return null; + + var uri = new UriBuilder(CombineUri(baseUri, $"submodels")); + if (pageLimit > 0) + uri.Query = $"Limit={pageLimit:D}"; + if (cursor != null) + uri.Query += $"&Cursor={cursor}"; + + return uri.Uri; + } + public static Uri BuildUriForSubmodel(Uri baseUri, string id, bool encryptIds = true) { // access @@ -367,8 +386,10 @@ public override async Task LoadFromSourceAsync( // invalidate cursor data string cursor = null; - // start with a list of AAS? - if (IsValidUriForAllAAS(fullItemLocation)) + // start with a list of AAS or Submodels (very similar) + var isAllAAS = IsValidUriForAllAAS(fullItemLocation); + var isAllSM = IsValidUriForAllSubmodel(fullItemLocation); + if (isAllAAS || isAllSM) { await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: new Uri(fullItemLocation), @@ -383,6 +404,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( && resChilds.Count > 0) { int childsToSkip = Math.Max(0, record.PageSkip); + bool firstNonSkipped = true; foreach (var n2 in resChilds) // second try to reduce side effects @@ -396,7 +418,13 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // on last child, attach side info for fetch prev/ next cursor AasIdentifiableSideInfo si = null; - // if (n2 == resChilds.First() && record.Pa) + if (firstNonSkipped && record.PageOffset > 0) + si = new AasIdentifiableSideInfo() + { + IsStub = false, + ShowCursorAbove = true + }; + firstNonSkipped = false; if (n2 == resChilds.Last() && record.PageLimit > 0) si = new AasIdentifiableSideInfo() @@ -406,9 +434,14 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( }; // add - prepAas.Add( - Jsonization.Deserialize.AssetAdministrationShellFrom(n2), - si); + if (isAllAAS) + prepAas.Add( + Jsonization.Deserialize.AssetAdministrationShellFrom(n2), + si); + if (isAllSM) + prepSM.Add( + Jsonization.Deserialize.SubmodelFrom(n2), + si); } catch (Exception ex) { @@ -576,8 +609,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( EnvDynPack?.SetContext(new PackageContainerHttpRepoSubsetFetchContext() { Record = record, - Cursor = cursor, - PageOffset = 0 // by definition + Cursor = cursor }); } @@ -619,18 +651,21 @@ public class ConnectExtendedRecord public enum BaseTypeEnum { Repository, Registry } public static string[] BaseTypeEnumNames = new[] { "Repository", "Registry" }; - public string BaseAddress = "https://eis-data.aas-voyager.com/"; + public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; + // public string BaseAddress = "https://eis-data.aas-voyager.com/"; // public string BaseAddress = "http://smt-repo.admin-shell-io.com/"; public BaseTypeEnum BaseType = BaseTypeEnum.Repository; - public bool GetAllAas = true; + public bool GetAllAas; public bool GetSingleAas; public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; - public bool GetSingleSubmodel; - public string SmId; + public bool GetAllSubmodel; + + public bool GetSingleSubmodel = true; + public string SmId = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ=="; public bool GetSingleCD; public string CdId; @@ -642,19 +677,33 @@ public enum BaseTypeEnum { Repository, Registry } public bool AutoLoadCds = true; public bool AutoLoadThumbnails = true; public bool AutoLoadOnDemand = true; - public bool EncryptIds = true; + public bool EncryptIds = false; public bool StayConnected; + /// + /// Pagenation. Limit to n resulsts. + /// public int PageLimit = 6; + + /// + /// When fetching, skip first n elements of the results + /// public int PageSkip = 0; + /// + /// This offset in elements is computed by this client by "counting". It does NOT come form + /// the server! + /// + public int PageOffset; + public void SetQueryChoices(int choice) { GetAllAas = (choice == 1); GetSingleAas = (choice == 2); - GetSingleSubmodel = (choice == 3); - GetSingleCD = (choice == 4); - ExecuteQuery = (choice == 5); + GetAllSubmodel = (choice == 3); + GetSingleSubmodel = (choice == 4); + GetSingleCD = (choice == 5); + ExecuteQuery = (choice == 6); } public string GetBaseTypStr() @@ -669,6 +718,7 @@ public string GetFetchOperationStr() if (GetAllAas) res = "GetAllAssetAdministrationShells"; if (GetSingleAas) res = "GetAssetAdministrationShellById"; + if (GetAllSubmodel) res = "GetAllSubmodels"; if (GetSingleSubmodel) res = "GetSubmodelById"; if (GetSingleCD) res = "GetConceptDescriptionById"; if (ExecuteQuery) res = "ExecuteQuery"; @@ -721,6 +771,14 @@ public static string BuildLocationFrom( return uri.ToString(); } + // All Submodels? + if (record.GetAllSubmodel) + { + // if a skip has been requested, these AAS need to be loaded, as well + var uri = BuildUriForAllSubmodel(baseUri, record.PageLimit + record.PageSkip, cursor); + return uri.ToString(); + } + // Single Submodel? if (record.GetSingleSubmodel) { @@ -895,6 +953,23 @@ public static async Task PerformConnectExtendedDialogue( row += 2; + // All Submodels + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get all Submodels", + isChecked: record.GetAllSubmodel, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => { + if ((bool)o) + record.SetQueryChoices(3); + else + record.GetAllSubmodel = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + row++; + // Single Submodel AnyUiUIElement.RegisterControl( helper.Set( @@ -905,7 +980,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(3); + record.SetQueryChoices(4); else record.GetSingleSubmodel = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -936,7 +1011,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(4); + record.SetQueryChoices(5); else record.GetSingleCD = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -967,7 +1042,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(5); + record.SetQueryChoices(6); else record.ExecuteQuery = false; return new AnyUiLambdaActionModalPanelReRender(uc); diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 1d68f2f40..b8b78e5ac 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -671,6 +671,13 @@ public VisualElementEnvironmentItem( this.Info += "" + thePackage.Filename; } } + + if (theItemType == ItemType.FetchPrev) + { + TagString = " \u21c8 "; + TagWeight = FontWeights.Normal; + } + if (theItemType == ItemType.FetchNext) { TagString = " \u21ca "; @@ -2469,6 +2476,15 @@ public void AddVisualElementsFromShellEnv( } }; + // display elements above? + if (si?.ShowCursorAbove == true) + { + lambdaAdd(new VisualElementEnvironmentItem( + null /* Parent */, cache, package, env, + VisualElementEnvironmentItem.ItemType.FetchPrev, + mainDataObject: null)); + } + // add item lambdaAdd(tiAas); @@ -2498,23 +2514,33 @@ public void AddVisualElementsFromShellEnv( { for (int smi=0; smi < env.Submodels.Count; smi++) { + // get info(s) + var sm = env.Submodels[smi]; + var si = (env.Submodels is + OnDemandListIdentifiable dynSms) + ? dynSms.GetSideInfo(smi) : null; + // check if Submodel with only side info is present - if (env.Submodels[smi] == null && env.Submodels is OnDemandList smsi) + if (si != null && si.IsStub) { - var si = smsi.GetSideInfo(smi); - if (si != null && si.IsStub) - { - // add stub - var tiSmSi = new VisualElementSubmodelStub(tiAllSubmodels, cache, package, si); - tiAllSubmodels.Members.Add(tiSmSi); + // add stub + var tiSmSi = new VisualElementSubmodelStub(tiAllSubmodels, cache, package, si); + tiAllSubmodels.Members.Add(tiSmSi); - // not further here!! - continue; - } + // not further here!! + continue; + } + + // display elements above? + if (si?.ShowCursorAbove == true) + { + tiAllSubmodels.Members.Add(new VisualElementEnvironmentItem( + null /* Parent */, cache, package, env, + VisualElementEnvironmentItem.ItemType.FetchPrev, + mainDataObject: null)); } // Submodel - var sm = env.Submodels[smi]; var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, env, sm); tiSm.SetIsExpandedIfNotTouched(expandMode > 1); tiAllSubmodels.Members.Add(tiSm); @@ -2541,6 +2567,15 @@ public void AddVisualElementsFromShellEnv( } } } + + // display elements above? + if (si?.ShowCursorBelow == true) + { + tiAllSubmodels.Members.Add(new VisualElementEnvironmentItem( + null /* Parent */, cache, package, env, + VisualElementEnvironmentItem.ItemType.FetchNext, + mainDataObject: null)); + } } } From 1548df97dd24938eb7782ae8840617fdd392aea1 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 6 Oct 2024 13:22:32 +0200 Subject: [PATCH 11/99] * SME under SM seems to work --- .../PackageContainerHttpRepoSubset.cs | 10 ++--- src/AasxPackageLogic/VisualAasxElements.cs | 43 ++++++++++++++++--- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index d0484cdfb..1e7740715 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -651,8 +651,8 @@ public class ConnectExtendedRecord public enum BaseTypeEnum { Repository, Registry } public static string[] BaseTypeEnumNames = new[] { "Repository", "Registry" }; - public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; - // public string BaseAddress = "https://eis-data.aas-voyager.com/"; + // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; + public string BaseAddress = "https://eis-data.aas-voyager.com/"; // public string BaseAddress = "http://smt-repo.admin-shell-io.com/"; public BaseTypeEnum BaseType = BaseTypeEnum.Repository; @@ -662,9 +662,9 @@ public enum BaseTypeEnum { Repository, Registry } public bool GetSingleAas; public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; - public bool GetAllSubmodel; + public bool GetAllSubmodel = true; - public bool GetSingleSubmodel = true; + public bool GetSingleSubmodel; public string SmId = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ=="; public bool GetSingleCD; @@ -683,7 +683,7 @@ public enum BaseTypeEnum { Repository, Registry } /// /// Pagenation. Limit to n resulsts. /// - public int PageLimit = 6; + public int PageLimit = 12; /// /// When fetching, skip first n elements of the results diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index b8b78e5ac..0c0c60fdb 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -921,16 +921,18 @@ public override void RefreshFromMainData() public class VisualElementSubmodel : VisualElementGeneric, ITaintableIdentifiable { + public AdminShellPackageEnvBase thePackage = null; public Aas.IEnvironment theEnv = null; public Aas.ISubmodel theSubmodel = null; public VisualElementSubmodel( - VisualElementGeneric parent, TreeViewLineCache cache, Aas.IEnvironment env, + VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnvBase package, Aas.IEnvironment env, Aas.ISubmodel sm) : base() { this.Parent = parent; this.Cache = cache; + this.thePackage = package; this.theEnv = env; this.theSubmodel = sm; @@ -1962,10 +1964,10 @@ private VisualElementGeneric GenerateVisualElementsFromShellEnvAddElements( return ti; } - private void GenerateInnerElementsForSubmodelRef( + private void GenerateInnerElementsForSubmodelOrRef( TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package, Aas.ISubmodel sm, - VisualElementSubmodelRef tiSm) + VisualElementGeneric tiSm) { // access if (sm == null || tiSm == null) @@ -2024,7 +2026,7 @@ private VisualElementSubmodelRef GenerateVisuElemForVisualElementSubmodelRef( else { // inner items directly - GenerateInnerElementsForSubmodelRef(cache, env, package, sm, tiSm); + GenerateInnerElementsForSubmodelOrRef(cache, env, package, sm, tiSm); } // ok @@ -2372,6 +2374,7 @@ public void AddVisualElementsFromShellEnv( // remember options OptionExpandMode = expandMode; OptionLazyLoadingFirst = lazyLoadingFirst; + var addSMEtoSME = env.AssetAdministrationShellCount() < 1; // quickly connect the Identifiables to the environment // and index them in order to quickly look them up @@ -2541,10 +2544,26 @@ public void AddVisualElementsFromShellEnv( } // Submodel - var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, env, sm); + var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, package, env, sm); tiSm.SetIsExpandedIfNotTouched(expandMode > 1); tiAllSubmodels.Members.Add(tiSm); + // add SME to Submodel? + if (addSMEtoSME) + { + if (true) + { + // set lazy loading first + tiSm.IsExpanded = false; + SetElementToLazyLoading(cache, env, package, tiSm); + } + else + { + // inner items directly + GenerateInnerElementsForSubmodelOrRef(cache, env, package, sm, tiSm); + } + } + // render ConceptDescriptions? if (tiCDs.CdSortOrder == VisualElementEnvironmentItem.ConceptDescSortOrder.BySubmodel) { @@ -2680,13 +2699,24 @@ public void ExecuteLazyLoading(VisualElementGeneric ve, bool forceExpanded = fal if (ve is VisualElementSubmodelRef vesmr) { ve.Members.Clear(); - GenerateInnerElementsForSubmodelRef(vesmr.Cache, vesmr.theEnv, vesmr.thePackage, vesmr.theSubmodel, + GenerateInnerElementsForSubmodelOrRef(vesmr.Cache, vesmr.theEnv, vesmr.thePackage, vesmr.theSubmodel, vesmr); ve.RestoreFromCache(); if (forceExpanded) ve.IsExpanded = true; } + + if (ve is VisualElementSubmodel vesm) + { + ve.Members.Clear(); + GenerateInnerElementsForSubmodelOrRef(vesm.Cache, vesm.theEnv, vesm.thePackage, vesm.theSubmodel, + vesm); + + ve.RestoreFromCache(); + if (forceExpanded) + ve.IsExpanded = true; + } } // @@ -3130,6 +3160,7 @@ public bool UpdateByEvent( && veei.theItemType == VisualElementEnvironmentItem.ItemType.AllSubmodels))) { var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, + data.Container?.Env, data.Container?.Env?.AasEnv, thisSm); tiSm.SetIsExpandedIfNotTouched(false); tiAllSubmodels.Members.Add(tiSm); From 1a773b87fad2ff2876663f6a9ebfc86695881a64 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Wed, 9 Oct 2024 08:56:48 +0200 Subject: [PATCH 12/99] * query GraphQL for API V3.0 only Submodels initially working --- .../PackageContainerHttpRepoSubset.cs | 178 ++++++++++++++- .../PackageCentral/PackageHttpDownloadUtil.cs | 212 ++++++++++++++++++ 2 files changed, 380 insertions(+), 10 deletions(-) diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 1e7740715..d03d5def4 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -30,6 +30,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using Namotion.Reflection; using System.Text.Json.Nodes; using System.Linq; +using System.Web; namespace AasxPackageLogic.PackageCentral { @@ -185,7 +186,7 @@ public static bool IsValidUriForSingleCD(string location) public static bool IsValidUriForQuery(string location) { - var m = Regex.Match(location, @"^(http(|s))://(.*?)/aaspe-query/(.{1,9999})$", + var m = Regex.Match(location, @"^(http(|s))://(.*?)/graphql(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } @@ -338,9 +339,13 @@ public static Uri BuildUriForQuery(Uri baseUri, string query) if (query?.HasContent() != true) return null; - // try combine - var queryEnc = AdminShellUtil.Base64Encode(query); - return CombineUri(baseUri, $"aaspe-query/{queryEnc}"); + // For the time being, only POST is possible, therefore only + // endpoint name is required for the real call. + // However, lets store the query as BASE64 query parameter + var queryEnc = AdminShellUtil.Base64UrlEncode(query); + var uri = new UriBuilder(CombineUri(baseUri, $"graphql")); + uri.Query = $"query={queryEnc}"; + return uri.Uri; } public static Uri BuildUriForSubmodel(Uri baseUri, Aas.IReference submodelRef) @@ -354,6 +359,13 @@ public static Uri BuildUriForSubmodel(Uri baseUri, Aas.IReference submodelRef) return BuildUriForSubmodel(baseUri, submodelRef.Keys[0].Value); } + protected enum FetchItemType { SmUrl, SmId } + protected class FetchItem + { + public FetchItemType Type; + public string Value; + } + public override async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, @@ -525,6 +537,150 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( }); } + // start with a query? + if (IsValidUriForQuery(fullItemLocation)) + { + // try extract query from the location + var query = ""; + var quri = new Uri(fullItemLocation); + if (quri.Query?.HasContent() == true) + { + var pc = HttpUtility.ParseQueryString(quri.Query); + foreach (var key in pc.AllKeys) + if (key == "query") + query = AdminShellUtil.Base64UrlDecode(pc[key]); + } + + // if not, try to get from record + if (!query.HasContent() && record?.QueryScript?.HasContent() == true) + query = record.QueryScript; + + // error + if (!query.HasContent()) + { + runtimeOptions?.Log?.Error("Could not determine valid query script. Aborting!"); + return; + } + + // but, the query needs to be reformatted as JSON + // query = "{ searchSMs(expression: \"\"\"$LOG \"\"\") { url smId } }"; + // query = "{ searchSMs(expression: \"\"\"$LOG filter=or(str_contains(sm.IdShort, \"Technical\"), str_contains(sm.IdShort, \"Nameplate\")) \"\"\") { url smId } }"; + query = query.Replace("\\", "\\\\"); + query = query.Replace("\"", "\\\""); + query = query.Replace("\r", " "); + query = query.Replace("\n", " "); + var jsonQuery = $"{{ \"query\" : \"{query}\" }} "; + + // there are subsequent fetch operations necessary + var fetchItems = new List(); + + // HTTP POST + var statCode = await PackageHttpDownloadUtil.HttpPostRequestToMemoryStream( + sourceUri: new Uri(quri.GetLeftPart(UriPartial.Path)), + requestBody: jsonQuery, + requestContentType: "application/json", + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + if (node["data"]?["searchSMs"] is JsonArray smdata + && smdata.Count >= 1) + { + foreach (var smrec in smdata) + { + var url = smrec["url"]?.ToString(); + var smId = smrec["smId"]?.ToString(); + if (smId?.HasContent() == true) + fetchItems.Add(new FetchItem() { Type = FetchItemType.SmId, Value = smId }); + else + if (url?.HasContent() == true) + fetchItems.Add(new FetchItem() { Type = FetchItemType.SmUrl, Value = url }); + } + } + + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing graphql result set"); + } + }); + + if (statCode != HttpStatusCode.OK) + { + Log.Singleton.Error("Could not fetch new dynamic elements by graphql. Aborting!"); + Log.Singleton.Error(" POST request was: {0}", jsonQuery); + return; + } + + // only makes sense, if query returns something + if (fetchItems.Count < 1) + { + Log.Singleton.Info(StoredPrint.Color.Blue, "Query resulted in zero elements, " + + "which could be fetched. Aborting!"); + return; + } + + // skip items? + if (record.PageSkip > 0) + { + fetchItems.RemoveRange(0, Math.Min(fetchItems.Count, record.PageSkip)); + } + var numItem = 0; + + // TODO: convert to parallel for each async + var dlErrors = 0; + foreach (var fi in fetchItems) + { + // reached end + numItem++; + if (record.PageLimit > 0 && numItem > record.PageLimit) + break; + + // prepare download + Uri loc = null; + if (fi.Type == FetchItemType.SmUrl) + loc = new Uri(fi.Value); + if (fi.Type == FetchItemType.SmId) + loc = BuildUriForSubmodel(baseUri, fi.Value, encryptIds: true); + + if (loc == null) + continue; + + // download (and skip errors) + try + { + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: loc, + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + if (fi.Type == FetchItemType.SmUrl || fi.Type == FetchItemType.SmId) + prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); + } + catch (Exception ex) + { + dlErrors++; + runtimeOptions?.Log?.Error(ex, "Parsing individual fetch element of query."); + } + }); + } catch (Exception ex) + { + dlErrors++; + LogInternally.That.CompletelyIgnoredError(ex); + } + } + + Log.Singleton.Info(StoredPrint.Color.Blue, "Executed GraphQL query. Receiving list of {0} elements, " + + "found {1} errors when individually downloading elements.", fetchItems.Count, dlErrors); + } + // start auto-load missing Submodels? if (record?.AutoLoadSubmodels ?? false) { @@ -651,9 +807,10 @@ public class ConnectExtendedRecord public enum BaseTypeEnum { Repository, Registry } public static string[] BaseTypeEnumNames = new[] { "Repository", "Registry" }; - // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; - public string BaseAddress = "https://eis-data.aas-voyager.com/"; + public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; + // public string BaseAddress = "https://eis-data.aas-voyager.com/"; // public string BaseAddress = "http://smt-repo.admin-shell-io.com/"; + // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; public BaseTypeEnum BaseType = BaseTypeEnum.Repository; @@ -662,7 +819,7 @@ public enum BaseTypeEnum { Repository, Registry } public bool GetSingleAas; public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; - public bool GetAllSubmodel = true; + public bool GetAllSubmodel; public bool GetSingleSubmodel; public string SmId = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ=="; @@ -670,8 +827,9 @@ public enum BaseTypeEnum { Repository, Registry } public bool GetSingleCD; public string CdId; - public bool ExecuteQuery; - public string QueryScript; + public bool ExecuteQuery = true; + // public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG\r\n filter=\r\n or(\r\n str_contains(sm.IdShort, \"Technical\"),\r\n str_contains(sm.IdShort, \"Nameplate\")\r\n )\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; + public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG$QL\r\n ( contains(sm.idShort, \"Technical\") and\r\n sme.value ge 100 and\r\n sme.value le 200 )\r\n or\r\n ( contains(sm.idShort, \"Nameplate\") and\r\n contains(sme.idShort,\"ManufacturerName\") and\r\n not(contains(sme.value,\"Phoenix\")))\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; public bool AutoLoadSubmodels = true; public bool AutoLoadCds = true; @@ -683,7 +841,7 @@ public enum BaseTypeEnum { Repository, Registry } /// /// Pagenation. Limit to n resulsts. /// - public int PageLimit = 12; + public int PageLimit = 4; /// /// When fetching, skip first n elements of the results diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index 4c4505603..101d703fa 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -286,6 +286,218 @@ await memStream.WriteAsync(buffer, 0, bytesRead, } } + public static async Task HttpPostRequestToMemoryStream( + Uri sourceUri, + string requestContentType, + string requestBody, + Action lambdaDownloadDone, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null, + bool allowFakeResponses = false) + { + // access + if (sourceUri == null) + return null; + + // read via HttpClient (uses standard proxies) + var handler = new HttpClientHandler(); + handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; + handler.AllowAutoRedirect = false; + + var client = new HttpClient(handler); + + // client.DefaultRequestHeaders.Add("Accept", "application/json"); + client.BaseAddress = new Uri(sourceUri.GetLeftPart(UriPartial.Authority)); + var requestPath = sourceUri.PathAndQuery; + + // Log + runtimeOptions?.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + + $"and request {requestPath} .. "); + + // Token existing? + var clhttp = containerList as PackageContainerListHttpRestBase; + var oidc = clhttp?.OpenIdClient; + if (oidc == null) + { + runtimeOptions?.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (clhttp != null && OpenIDClient.email != "") + { + clhttp.OpenIdClient = new OpenIdClientInstance(); + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + oidc = clhttp.OpenIdClient; + } + } + if (oidc != null) + { + if (oidc.token != "") + { + runtimeOptions?.Log?.Info($" using existing bearer token."); + client.SetBearerToken(oidc.token); + } + else + { + if (oidc.email != "") + { + runtimeOptions?.Log?.Info($" using existing email token."); + client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); + } + } + } + + // prepare request + var requestContent = new StringContent(requestBody, Encoding.UTF8, requestContentType); + + // retrieve response + bool repeat = true; + + while (repeat) + { + // get response? + var response = await client.PostAsync(requestPath, requestContent); + + if (clhttp != null + && response.StatusCode == System.Net.HttpStatusCode.TemporaryRedirect) + { + string redirectUrl = response.Headers.Location.ToString(); + // ReSharper disable once RedundantExplicitArrayCreation + string[] splitResult = redirectUrl.Split(new string[] { "?" }, + StringSplitOptions.RemoveEmptyEntries); + splitResult[0] = splitResult[0].TrimEnd('/'); + + if (splitResult.Length < 1) + { + runtimeOptions?.Log?.Error("TemporaryRedirect, but url split to successful"); + break; + } + + runtimeOptions?.Log?.Info("Redirect to: " + splitResult[0]); + + if (oidc == null) + { + runtimeOptions?.Log?.Info("Creating new OpenIdClient.."); + oidc = new OpenIdClientInstance(); + clhttp.OpenIdClient = oidc; + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + } + + oidc.authServer = splitResult[0]; + + runtimeOptions?.Log?.Info($".. authentication at auth server {oidc.authServer} needed"); + + var response2 = await oidc.RequestTokenAsync(null, + GenerateUiLambdaSet(runtimeOptions)); + if (oidc.keycloak == "" && response2 != null) + oidc.token = response2.AccessToken; + if (oidc.token != "" && oidc.token != null) + client.SetBearerToken(oidc.token); + + repeat = true; + continue; + } + + repeat = false; + + if (response.IsSuccessStatusCode) + { + // + // this portion of the code is prepared to receive large sets of content data + // + + var contentLength = response.Content.Headers.ContentLength; + var contentFn = response.Content.Headers.ContentDisposition?.FileName; + + // log + runtimeOptions?.Log?.Info($".. response with header-content-len {contentLength} " + + $"and file-name {contentFn} .."); + + var contentStream = await response?.Content?.ReadAsStreamAsync(); + if (contentStream == null) + throw new PackageContainerException( + $"While getting data bytes from {sourceUri.ToString()} via HttpClient " + + $"no data-content was responded!"); + + // create temp file and write to it + var givenFn = sourceUri.ToString(); + if (contentFn != null) + givenFn = contentFn; + runtimeOptions?.Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " + + $"and request {requestPath} .. "); + + using (var memStream = new MemoryStream()) + { + // copy with progress + var bufferSize = 4024; + var deltaSize = 512 * 1024; + var buffer = new byte[bufferSize]; + long totalBytesRead = 0; + long lastBytesRead = 0; + int bytesRead; + + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Starting, + contentLength, totalBytesRead); + + while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, + default(CancellationToken)).ConfigureAwait(false)) != 0) + { + await memStream.WriteAsync(buffer, 0, bytesRead, + default(CancellationToken)).ConfigureAwait(false); + + totalBytesRead += bytesRead; + + if (totalBytesRead > lastBytesRead + deltaSize) + { + runtimeOptions?.Log?.Info($".. downloading to memory stream"); + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Ongoing, + contentLength, totalBytesRead); + lastBytesRead = totalBytesRead; + } + } + + // assume bytes read to be total bytes + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Final, + totalBytesRead, totalBytesRead); + + // log + runtimeOptions?.Log?.Info($".. download done with {totalBytesRead} bytes read!"); + + // execute lambda + memStream.Flush(); + memStream.Seek(0, SeekOrigin.Begin); + lambdaDownloadDone?.Invoke(memStream, contentFn); + + // good + return HttpStatusCode.OK; + } + } + else + { + // + // assume smaller conent data + // + var responseContents = await response.Content.ReadAsStringAsync(); + + if (response.StatusCode == HttpStatusCode.NotFound) + { + // not found but also no error: intentionally do nothing + return response.StatusCode; + } + else + { + Log.Singleton.Error($"HttpPostRequestToMemoryStream server gave status code " + + $"{(int) response.StatusCode} {response.StatusCode}!"); + Log.Singleton.Error(" response content: {0}", responseContents); + return response.StatusCode; + } + } + } + + return null; + } + public static async Task HttpPutFromMemoryStream( MemoryStream ms, Uri destUri, From 4936603cbac83b896477682f83af4741fb3c4d98 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sat, 12 Oct 2024 15:50:07 +0200 Subject: [PATCH 13/99] * messing up to much async --- src/AasxCsharpLibrary/AdminShellComparers.cs | 57 + .../PackageCentral/AasOnDemandEnvironment.cs | 1 + .../AdminShellPackageDynamicFetchEnv.cs | 14 +- .../PackageContainerHttpRepoSubset.cs | 1117 +++++++++++------ .../PackageCentral/PackageHttpDownloadUtil.cs | 217 +++- 5 files changed, 1022 insertions(+), 384 deletions(-) create mode 100644 src/AasxCsharpLibrary/AdminShellComparers.cs diff --git a/src/AasxCsharpLibrary/AdminShellComparers.cs b/src/AasxCsharpLibrary/AdminShellComparers.cs new file mode 100644 index 000000000..d8ff939c4 --- /dev/null +++ b/src/AasxCsharpLibrary/AdminShellComparers.cs @@ -0,0 +1,57 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + +using Extensions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace AdminShellNS +{ + public static class AdminShellComparers + { + + /// see: https://www.codeproject.com/Articles/72470/LINQ-Enhancing-Distinct-With-The-PredicateEquality + public class PredicateEqualityComparer : EqualityComparer + { + private Func predicate; + + public PredicateEqualityComparer(Func predicate) + : base() + { + this.predicate = predicate; + } + + public override bool Equals(T x, T y) + { + if (x != null) + { + return ((y != null) && this.predicate(x, y)); + } + + if (y != null) + { + return false; + } + + return true; + } + + public override int GetHashCode(T obj) + { + // Always return the same value to force the call to IEqualityComparer.Equals + return 0; + } + } + } +} diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index 6b55462de..22d75c217 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -42,6 +42,7 @@ public class AasIdentifiableSideInfo : OnDemandSideInfoBase public string Id = ""; public string IdShort = ""; + public Uri Endpoint; /// /// In the tree of virtual elements, place a visual element below this entity diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index 0a3467fd1..c0d2a73ea 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -84,8 +84,8 @@ public async Task TryFetchThumbnail(Aas.IAssetAdministrationShell aas) // try try { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: PackageContainerHttpRepoSubset.BuildUriForAasThumbnail(_defaultRepoBaseUri, aas.Id), + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + sourceUri: PackageContainerHttpRepoSubset.BuildUriForRepoAasThumbnail(_defaultRepoBaseUri, aas.Id), runtimeOptions: _runtimeOptions, lambdaDownloadDone: (ms, contentFn) => { @@ -129,11 +129,11 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( Aas.IIdentifiable res = null; // build the location - var loc = PackageContainerHttpRepoSubset.BuildUriForSubmodel(_defaultRepoBaseUri, id); + var loc = PackageContainerHttpRepoSubset.BuildUriForRepoSingleSubmodel(_defaultRepoBaseUri, id); if (loc == null) return null; - await PackageHttpDownloadUtil.HttpGetToMemoryStream( + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( sourceUri: loc, allowFakeResponses: _runtimeOptions?.AllowFakeResponses ?? false, runtimeOptions: _runtimeOptions, @@ -317,15 +317,15 @@ public async Task TrySaveAllTaintedIdentifiables( if (allAas) count += await TrySaveAllTaintedIdentifiablesOf( _aasEnv?.AssetAdministrationShells, - (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForAAS(defBase, id)); + (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForRepoSingleAAS(defBase, id)); if (allSubmodels) count += await TrySaveAllTaintedIdentifiablesOf( _aasEnv?.Submodels, - (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForSubmodel(defBase, id)); + (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForRepoSingleSubmodel(defBase, id)); if (allCDs) count += await TrySaveAllTaintedIdentifiablesOf( _aasEnv?.ConceptDescriptions, - (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForCD(defBase, id)); + (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForRepoSingleCD(defBase, id)); return count; } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index d03d5def4..77e5faa5f 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -31,6 +31,9 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Text.Json.Nodes; using System.Linq; using System.Web; +using Newtonsoft.Json; +using System.Dynamic; +using Newtonsoft.Json.Linq; namespace AasxPackageLogic.PackageCentral { @@ -144,28 +147,32 @@ public override string ToString() return "HTTP Repository element: " + Location; } - public static bool IsValidUriForAllAAS(string location) + // + // REPO + // + + public static bool IsValidUriForRepoAllAAS(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } - public static bool IsValidUriForSingleAAS(string location) + public static bool IsValidUriForRepoSingleAAS(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells/([^?]{1,99})", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } - public static bool IsValidUriForAllSubmodel(string location) + public static bool IsValidUriForRepoAllSubmodel(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } - public static bool IsValidUriForSingleSubmodel(string location) + public static bool IsValidUriForRepoSingleSubmodel(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels/(.{1,99})$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); @@ -174,31 +181,46 @@ public static bool IsValidUriForSingleSubmodel(string location) // TODO: Add AAS based Submodel return false; - } - public static bool IsValidUriForSingleCD(string location) + public static bool IsValidUriForRepoSingleCD(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/conceptdescriptions/(.{1,99})$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } - public static bool IsValidUriForQuery(string location) + public static bool IsValidUriForRepoQuery(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/graphql(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } + // + // REGISTRY + // + + public static bool IsValidUriForRegistryAllAAS(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/shell-descriptors(|/|/?\?(.*))$", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + + // + // ALL + // + public static bool IsValidUriAnyMatch(string location) { - return IsValidUriForAllAAS(location) - || IsValidUriForSingleAAS(location) - || IsValidUriForAllSubmodel(location) - || IsValidUriForSingleSubmodel(location) - || IsValidUriForSingleCD(location) - || IsValidUriForQuery(location); + return IsValidUriForRepoAllAAS(location) + || IsValidUriForRepoSingleAAS(location) + || IsValidUriForRepoAllSubmodel(location) + || IsValidUriForRepoSingleSubmodel(location) + || IsValidUriForRepoSingleCD(location) + || IsValidUriForRepoQuery(location) + || IsValidUriForRegistryAllAAS(location); } public static Uri GetBaseUri(string location) @@ -224,18 +246,6 @@ public static Uri GetBaseUri(string location) return null; } - //public static string CombineUri (string uri1, string uri2) - //{ - // var res = "" + uri1; - // if (uri2?.HasContent() == true) - // { - // if (!res.EndsWith("/")) - // res += "/"; - // res += uri2; - // } - // return res; - //} - public static Uri CombineUri(Uri baseUri, string relativeUri) { if (baseUri == null || relativeUri?.HasContent() != true) @@ -247,7 +257,11 @@ public static Uri CombineUri(Uri baseUri, string relativeUri) return null; } - public static Uri BuildUriForAllAAS(Uri baseUri, int pageLimit = 100, string cursor = null) + // + // REPO + // + + public static Uri BuildUriForRepoAllAAS(Uri baseUri, int pageLimit = 100, string cursor = null) { // access if (baseUri == null) @@ -269,7 +283,7 @@ public static Uri BuildUriForAllAAS(Uri baseUri, int pageLimit = 100, string cur return uri.Uri; } - public static Uri BuildUriForAAS(Uri baseUri, string id, bool encryptIds = true) + public static Uri BuildUriForRepoSingleAAS(Uri baseUri, string id, bool encryptIds = true) { // access if (id?.HasContent() != true) @@ -280,7 +294,7 @@ public static Uri BuildUriForAAS(Uri baseUri, string id, bool encryptIds = true) return CombineUri(baseUri, $"shells/{smidenc}"); } - public static Uri BuildUriForAasThumbnail(Uri baseUri, string id, bool encryptIds = true) + public static Uri BuildUriForRepoAasThumbnail(Uri baseUri, string id, bool encryptIds = true) { // access if (id?.HasContent() != true) @@ -291,9 +305,9 @@ public static Uri BuildUriForAasThumbnail(Uri baseUri, string id, bool encryptId return CombineUri(baseUri, $"shells/{smidenc}/asset-information/thumbnail"); } - public static Uri BuildUriForAllSubmodel(Uri baseUri, int pageLimit = 100, string cursor = null) + public static Uri BuildUriForRepoAllSubmodel(Uri baseUri, int pageLimit = 100, string cursor = null) { - // for more info: see BuildUriForAllAAS + // for more info: see BuildUriForRepoAllAAS // access if (baseUri == null) return null; @@ -307,7 +321,7 @@ public static Uri BuildUriForAllSubmodel(Uri baseUri, int pageLimit = 100, strin return uri.Uri; } - public static Uri BuildUriForSubmodel(Uri baseUri, string id, bool encryptIds = true) + public static Uri BuildUriForRepoSingleSubmodel(Uri baseUri, string id, bool encryptIds = true) { // access if (id?.HasContent() != true) @@ -318,7 +332,18 @@ public static Uri BuildUriForSubmodel(Uri baseUri, string id, bool encryptIds = return CombineUri(baseUri, $"submodels/{smidenc}"); } - public static Uri BuildUriForCD(Uri baseUri, string id, bool encryptIds = true) + public static Uri BuildUriForRepoSingleSubmodel(Uri baseUri, Aas.IReference submodelRef) + { + // access + if (baseUri == null || submodelRef?.IsValid() != true + || submodelRef.Count() != 1 || submodelRef.Keys[0].Type != KeyTypes.Submodel) + return null; + + // pass on + return BuildUriForRepoSingleSubmodel(baseUri, submodelRef.Keys[0].Value); + } + + public static Uri BuildUriForRepoSingleCD(Uri baseUri, string id, bool encryptIds = true) { // access if (id?.HasContent() != true) @@ -333,7 +358,7 @@ public static Uri BuildUriForCD(Uri baseUri, string id, bool encryptIds = true) /// Note: this is an AASPE specific, proprietary extension. /// This REST ressource does not exist in the official specification! /// - public static Uri BuildUriForQuery(Uri baseUri, string query) + public static Uri BuildUriForRepoQuery(Uri baseUri, string query) { // access if (query?.HasContent() != true) @@ -348,17 +373,30 @@ public static Uri BuildUriForQuery(Uri baseUri, string query) return uri.Uri; } - public static Uri BuildUriForSubmodel(Uri baseUri, Aas.IReference submodelRef) + // + // REGISTRY + // + + public static Uri BuildUriForRegistryAllAAS(Uri baseUri, int pageLimit = 100, string cursor = null) { + // for more info: see BuildUriForRepoAllAAS // access - if (baseUri == null || submodelRef?.IsValid() != true - || submodelRef.Count() != 1 || submodelRef.Keys[0].Type != KeyTypes.Submodel) + if (baseUri == null) return null; - // pass on - return BuildUriForSubmodel(baseUri, submodelRef.Keys[0].Value); + var uri = new UriBuilder(CombineUri(baseUri, $"shell-descriptors")); + if (pageLimit > 0) + uri.Query = $"Limit={pageLimit:D}"; + if (cursor != null) + uri.Query += $"&Cursor={cursor}"; + + return uri.Uri; } + // + // ALL + // + protected enum FetchItemType { SmUrl, SmId } protected class FetchItem { @@ -366,6 +404,145 @@ protected class FetchItem public string Value; } + /// see: https://stackoverflow.com/questions/9956648/how-do-i-check-if-a-property-exists-on-a-dynamic-anonymous-type-in-c + /// see: https://stackoverflow.com/questions/63972270/newtonsoft-json-check-if-property-and-its-value-exists + public static bool HasProperty(dynamic obj, string name) + { + Type objType = obj.GetType(); + + if (obj is Newtonsoft.Json.Linq.JObject jo) + { + return jo.ContainsKey(name); + } + + if (objType == typeof(ExpandoObject)) + { + return ((IDictionary)obj).ContainsKey(name); + } + + return objType.GetProperty(name) != null; + } + + /// + /// This utility is able to parallel download Identifiables and will call lambda upon. + /// Insted of a list of location, it is taking a list of objects (entities) and a lambda + /// to extract the location from. + /// + /// Type of Identifiable + /// Type of entity element + protected async Task DownloadListOfIdentifiables( + IEnumerable entities, + Func lambdaGetLocation, + Action lambdaDownloadDoneOrFail, + PackCntRuntimeOptions runtimeOptions = null, + bool allowFakeResponses = false, + bool useParallel = false) where T : Aas.IIdentifiable + { + // access + if (entities == null) + return 0; + + // result + int numRes = 0; + + // lambda for deserialize + Func lambdaDeserialize = (node) => + { + if (typeof(T).IsAssignableFrom(typeof(Aas.IAssetAdministrationShell))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.AssetAdministrationShellFrom(node)); + if (typeof(T).IsAssignableFrom(typeof(Aas.ISubmodel))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.SubmodelFrom(node)); + if (typeof(T).IsAssignableFrom(typeof(Aas.IConceptDescription))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.ConceptDescriptionFrom(node)); + return default(T); + }; + + // over all locations + if (!useParallel) + { + foreach (var ent in entities) + { + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: lambdaGetLocation?.Invoke(ent), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: async (code, ms, contentFn) => + { + await Task.Yield(); + + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + T idf = lambdaDeserialize(node); + lambdaDownloadDoneOrFail?.Invoke(code, idf, contentFn, ent); + if (code == HttpStatusCode.OK) + numRes++; + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, $"Parsing downloaded {typeof(T).GetDisplayName()}"); + } + }); + } + } else + { + await Parallel.ForEachAsync(entities, + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (ent, token) => + { + var thisEnt = ent; + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: lambdaGetLocation?.Invoke(ent), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: async (code, ms, contentFn) => + { + await Task.Yield(); + + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + T idf = lambdaDeserialize(node); + lambdaDownloadDoneOrFail?.Invoke(code, idf, contentFn, thisEnt); + if (code == HttpStatusCode.OK) + numRes++; + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, $"Parsing downloaded {typeof(T).GetDisplayName()}"); + } + }); + }); + + } + + // ok + return numRes; + } + + protected async Task DownloadIdentifiableToOK( + Uri location, + PackCntRuntimeOptions runtimeOptions = null, + bool allowFakeResponses = false) where T : Aas.IIdentifiable + { + T res = default(T); + + await DownloadListOfIdentifiables( + new[] { location }, + lambdaGetLocation: (loc) => loc, + runtimeOptions: runtimeOptions, + allowFakeResponses: allowFakeResponses, + lambdaDownloadDoneOrFail: async (code, idf, contentFn, ent) => + { + await Task.Yield(); + + if (code == HttpStatusCode.OK) + res = idf; + }); + + return res; + } + public override async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, @@ -398,358 +575,520 @@ public override async Task LoadFromSourceAsync( // invalidate cursor data string cursor = null; - // start with a list of AAS or Submodels (very similar) - var isAllAAS = IsValidUriForAllAAS(fullItemLocation); - var isAllSM = IsValidUriForAllSubmodel(fullItemLocation); - if (isAllAAS || isAllSM) + // TODO: very long function, needs to be refactored + + // + // REGISTRY + // + + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: new Uri(fullItemLocation), - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => - { - try + // AAS descriptors? + if (IsValidUriForRegistryAllAAS(fullItemLocation)) + { + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: async (code, ms, contentFn) => { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - if (node["result"] is JsonArray resChilds - && resChilds.Count > 0) + if (code != HttpStatusCode.OK) + return; + + try { - int childsToSkip = Math.Max(0, record.PageSkip); - bool firstNonSkipped = true; + // try working with dynamic objects + using (StreamReader reader = new StreamReader(ms, System.Text.Encoding.UTF8, true)) + using (var jsonTextReader = new JsonTextReader(reader)) + { + JsonSerializer serializer = new JsonSerializer(); + dynamic resObj = serializer.Deserialize(jsonTextReader); - foreach (var n2 in resChilds) - // second try to reduce side effects - try + // do the naive approach first und simply go ahead with dynamic parsing + // these data + foreach (var res in resObj.result) { - if (childsToSkip > 0) + foreach (var ep in res.endpoints) { - childsToSkip--; - continue; - } - - // on last child, attach side info for fetch prev/ next cursor - AasIdentifiableSideInfo si = null; - if (firstNonSkipped && record.PageOffset > 0) - si = new AasIdentifiableSideInfo() + // strictly check IFC + var aasIfc = "" + ep["interface"]; + if (aasIfc != "AAS-1.0") + continue; + + // direct access HREF + var aasUri = new Uri("" + ep.protocolInformation.href); + + // but in order to operate as registry, a list if Submodel endpoints + // is required as well + var smRegged = new List(); + if (HasProperty(res, "submodelDescriptors")) + foreach (var smdesc in res.submodelDescriptors) + { + foreach (var smep in smdesc.endpoints) + { + // strictly check IFC + var smIfc = "" + smep["interface"]; + if (smIfc != "SUBMODEL-1.0") + continue; + + // ok + string href = smep.protocolInformation.href; + if (href.HasContent() == true) + smRegged.Add(new AasIdentifiableSideInfo() + { + IsStub = true, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = smdesc.id, + IdShort = smdesc.idshort, + Endpoint = new Uri(href) + }); + } + } + + // ok + var aas = await DownloadIdentifiableToOK(aasUri, + runtimeOptions, allowFakeResponses); + if (aas != null) { - IsStub = false, - ShowCursorAbove = true - }; - firstNonSkipped = false; - - if (n2 == resChilds.Last() && record.PageLimit > 0) - si = new AasIdentifiableSideInfo() - { - IsStub = false, - ShowCursorBelow = true - }; - - // add - if (isAllAAS) - prepAas.Add( - Jsonization.Deserialize.AssetAdministrationShellFrom(n2), - si); - if (isAllSM) - prepSM.Add( - Jsonization.Deserialize.SubmodelFrom(n2), - si); - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, "Parsing single AAS of list of all AAS"); + // found culprit: Submodels are listed twice (or more) in an AAS + // try filter + var uniqSms = aas.Submodels?.Distinct( + new AdminShellComparers.PredicateEqualityComparer( + (x, y) => x?.Matches(y, MatchMode.Relaxed) == true)) + ?? new List(); + + // make sure the list of Submodel endpoints is the same + // as the AAS expects + if (smRegged.Count != uniqSms.Count()) + { + Log.Singleton.Info(StoredPrint.Color.Blue, + "For downloading AAS at {0}, the number of Submodels " + + "was different to the number of given Submodel endpoints.", + aasUri.ToString()); + + // cycle to next endpoint or next descriptor (more likely) + // continue; + } + + // makes most sense to "recrate" the AAS.Submodels with the side infos + // from the registry + aas.Submodels = null; + foreach (var smrr in smRegged) + aas.AddSubmodelReference(new Aas.Reference( + ReferenceTypes.ModelReference, + (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, smrr.Id) }).ToList())); + + // add this AAS + prepAas.Add(aas, null); + + // check if to add the Submodels + // be prepared to download them + var numRes = await DownloadListOfIdentifiables( + smRegged, + lambdaGetLocation: (si) => si.Endpoint, + runtimeOptions: runtimeOptions, + allowFakeResponses: allowFakeResponses, + lambdaDownloadDoneOrFail: async (code, sm, contentFn, si) => + { + // error ? + if (code != HttpStatusCode.OK) + { + Log.Singleton.Error( + "Could not download Submodel from endpoint given by registry: {0}", + si.Endpoint.ToString()); + + // add as pure side info + si.IsStub = true; + prepSM.Add(null, si); + } + + // no, add with data + si.IsStub = false; + prepSM.Add(sm, si); + }); + } + + // ok + ; + } } + } } - - // cursor data - if (node["paging_metadata"] is JsonNode nodePaging - && nodePaging["cursor"] is JsonNode nodeCursor) + catch (Exception ex) { - cursor = nodeCursor.ToString(); + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); } - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, "Parsing list of all AAS"); - } - }); + }); + } } - // start with single AAS? - if (IsValidUriForSingleAAS(fullItemLocation)) - { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: new Uri(fullItemLocation), - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => - { - try - { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - prepAas.Add(Jsonization.Deserialize.AssetAdministrationShellFrom(node), null); - } catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); - } - }); - } + // + // REPO + // - // start with Submodel? - if (IsValidUriForSingleSubmodel(fullItemLocation)) + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: new Uri(fullItemLocation), - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => - { - try - { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); - } - catch (Exception ex) + // start with a list of AAS or Submodels (very similar) + var isAllAAS = IsValidUriForRepoAllAAS(fullItemLocation); + var isAllSM = IsValidUriForRepoAllSubmodel(fullItemLocation); + if (isAllAAS || isAllSM) + { + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => { - runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded Submodel"); - } - }); - } + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + if (node["result"] is JsonArray resChilds + && resChilds.Count > 0) + { + int childsToSkip = Math.Max(0, record.PageSkip); + bool firstNonSkipped = true; - // start with CD? - if (IsValidUriForSingleCD(fullItemLocation)) - { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: new Uri(fullItemLocation), - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => - { - try - { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - prepCD.Add(Jsonization.Deserialize.ConceptDescriptionFrom(node), null); - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded ConceptDescription"); - } - }); - } + foreach (var n2 in resChilds) + // second try to reduce side effects + try + { + if (childsToSkip > 0) + { + childsToSkip--; + continue; + } + + // on last child, attach side info for fetch prev/ next cursor + AasIdentifiableSideInfo si = null; + if (firstNonSkipped && record.PageOffset > 0) + si = new AasIdentifiableSideInfo() + { + IsStub = false, + ShowCursorAbove = true + }; + firstNonSkipped = false; + + if (n2 == resChilds.Last() && record.PageLimit > 0) + si = new AasIdentifiableSideInfo() + { + IsStub = false, + ShowCursorBelow = true + }; + + // add + if (isAllAAS) + prepAas.Add( + Jsonization.Deserialize.AssetAdministrationShellFrom(n2), + si); + if (isAllSM) + prepSM.Add( + Jsonization.Deserialize.SubmodelFrom(n2), + si); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing single AAS of list of all AAS"); + } + } - // start with a query? - if (IsValidUriForQuery(fullItemLocation)) - { - // try extract query from the location - var query = ""; - var quri = new Uri(fullItemLocation); - if (quri.Query?.HasContent() == true) - { - var pc = HttpUtility.ParseQueryString(quri.Query); - foreach (var key in pc.AllKeys) - if (key == "query") - query = AdminShellUtil.Base64UrlDecode(pc[key]); + // cursor data + if (node["paging_metadata"] is JsonNode nodePaging + && nodePaging["cursor"] is JsonNode nodeCursor) + { + cursor = nodeCursor.ToString(); + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing list of all AAS"); + } + }); } - // if not, try to get from record - if (!query.HasContent() && record?.QueryScript?.HasContent() == true) - query = record.QueryScript; - - // error - if (!query.HasContent()) + // start with single AAS? + if (IsValidUriForRepoSingleAAS(fullItemLocation)) { - runtimeOptions?.Log?.Error("Could not determine valid query script. Aborting!"); - return; - } - - // but, the query needs to be reformatted as JSON - // query = "{ searchSMs(expression: \"\"\"$LOG \"\"\") { url smId } }"; - // query = "{ searchSMs(expression: \"\"\"$LOG filter=or(str_contains(sm.IdShort, \"Technical\"), str_contains(sm.IdShort, \"Nameplate\")) \"\"\") { url smId } }"; - query = query.Replace("\\", "\\\\"); - query = query.Replace("\"", "\\\""); - query = query.Replace("\r", " "); - query = query.Replace("\n", " "); - var jsonQuery = $"{{ \"query\" : \"{query}\" }} "; - - // there are subsequent fetch operations necessary - var fetchItems = new List(); - - // HTTP POST - var statCode = await PackageHttpDownloadUtil.HttpPostRequestToMemoryStream( - sourceUri: new Uri(quri.GetLeftPart(UriPartial.Path)), - requestBody: jsonQuery, - requestContentType: "application/json", - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => - { - try + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - if (node["data"]?["searchSMs"] is JsonArray smdata - && smdata.Count >= 1) + try { - foreach (var smrec in smdata) - { - var url = smrec["url"]?.ToString(); - var smId = smrec["smId"]?.ToString(); - if (smId?.HasContent() == true) - fetchItems.Add(new FetchItem() { Type = FetchItemType.SmId, Value = smId }); - else - if (url?.HasContent() == true) - fetchItems.Add(new FetchItem() { Type = FetchItemType.SmUrl, Value = url }); - } + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + prepAas.Add(Jsonization.Deserialize.AssetAdministrationShellFrom(node), null); } - - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, "Parsing graphql result set"); - } - }); - - if (statCode != HttpStatusCode.OK) - { - Log.Singleton.Error("Could not fetch new dynamic elements by graphql. Aborting!"); - Log.Singleton.Error(" POST request was: {0}", jsonQuery); - return; + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); + } + }); } - // only makes sense, if query returns something - if (fetchItems.Count < 1) + // start with Submodel? + if (IsValidUriForRepoSingleSubmodel(fullItemLocation)) { - Log.Singleton.Info(StoredPrint.Color.Blue, "Query resulted in zero elements, " + - "which could be fetched. Aborting!"); - return; + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded Submodel"); + } + }); } - // skip items? - if (record.PageSkip > 0) + // start with CD? + if (IsValidUriForRepoSingleCD(fullItemLocation)) { - fetchItems.RemoveRange(0, Math.Min(fetchItems.Count, record.PageSkip)); + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + sourceUri: new Uri(fullItemLocation), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + prepCD.Add(Jsonization.Deserialize.ConceptDescriptionFrom(node), null); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded ConceptDescription"); + } + }); } - var numItem = 0; - // TODO: convert to parallel for each async - var dlErrors = 0; - foreach (var fi in fetchItems) + // start with a query? + if (IsValidUriForRepoQuery(fullItemLocation)) { - // reached end - numItem++; - if (record.PageLimit > 0 && numItem > record.PageLimit) - break; - - // prepare download - Uri loc = null; - if (fi.Type == FetchItemType.SmUrl) - loc = new Uri(fi.Value); - if (fi.Type == FetchItemType.SmId) - loc = BuildUriForSubmodel(baseUri, fi.Value, encryptIds: true); - - if (loc == null) - continue; - - // download (and skip errors) - try + // try extract query from the location + var query = ""; + var quri = new Uri(fullItemLocation); + if (quri.Query?.HasContent() == true) { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: loc, - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + var pc = HttpUtility.ParseQueryString(quri.Query); + foreach (var key in pc.AllKeys) + if (key == "query") + query = AdminShellUtil.Base64UrlDecode(pc[key]); + } + + // if not, try to get from record + if (!query.HasContent() && record?.QueryScript?.HasContent() == true) + query = record.QueryScript; + + // error + if (!query.HasContent()) + { + runtimeOptions?.Log?.Error("Could not determine valid query script. Aborting!"); + return; + } + + // but, the query needs to be reformatted as JSON + // query = "{ searchSMs(expression: \"\"\"$LOG \"\"\") { url smId } }"; + // query = "{ searchSMs(expression: \"\"\"$LOG filter=or(str_contains(sm.IdShort, \"Technical\"), str_contains(sm.IdShort, \"Nameplate\")) \"\"\") { url smId } }"; + query = query.Replace("\\", "\\\\"); + query = query.Replace("\"", "\\\""); + query = query.Replace("\r", " "); + query = query.Replace("\n", " "); + var jsonQuery = $"{{ \"query\" : \"{query}\" }} "; + + // there are subsequent fetch operations necessary + var fetchItems = new List(); + + // HTTP POST + var statCode = await PackageHttpDownloadUtil.HttpPostRequestToMemoryStream( + sourceUri: new Uri(quri.GetLeftPart(UriPartial.Path)), + requestBody: jsonQuery, + requestContentType: "application/json", + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => + { + try { - try + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + if (node["data"]?["searchSMs"] is JsonArray smdata + && smdata.Count >= 1) { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - if (fi.Type == FetchItemType.SmUrl || fi.Type == FetchItemType.SmId) - prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); - } - catch (Exception ex) - { - dlErrors++; - runtimeOptions?.Log?.Error(ex, "Parsing individual fetch element of query."); + foreach (var smrec in smdata) + { + var url = smrec["url"]?.ToString(); + var smId = smrec["smId"]?.ToString(); + if (smId?.HasContent() == true) + fetchItems.Add(new FetchItem() { Type = FetchItemType.SmId, Value = smId }); + else + if (url?.HasContent() == true) + fetchItems.Add(new FetchItem() { Type = FetchItemType.SmUrl, Value = url }); + } } - }); - } catch (Exception ex) + + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing graphql result set"); + } + }); + + if (statCode != HttpStatusCode.OK) { - dlErrors++; - LogInternally.That.CompletelyIgnoredError(ex); + Log.Singleton.Error("Could not fetch new dynamic elements by graphql. Aborting!"); + Log.Singleton.Error(" POST request was: {0}", jsonQuery); + return; } - } - Log.Singleton.Info(StoredPrint.Color.Blue, "Executed GraphQL query. Receiving list of {0} elements, " + - "found {1} errors when individually downloading elements.", fetchItems.Count, dlErrors); - } + // only makes sense, if query returns something + if (fetchItems.Count < 1) + { + Log.Singleton.Info(StoredPrint.Color.Blue, "Query resulted in zero elements, " + + "which could be fetched. Aborting!"); + return; + } - // start auto-load missing Submodels? - if (record?.AutoLoadSubmodels ?? false) - { - var lrs = env.FindAllSubmodelReferences(onlyNotExisting: true).ToList(); + // skip items? + if (record.PageSkip > 0) + { + fetchItems.RemoveRange(0, Math.Min(fetchItems.Count, record.PageSkip)); + } + var numItem = 0; - await Parallel.ForEachAsync(lrs, - new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, - async (lr, token) => + // TODO: convert to parallel for each async + var dlErrors = 0; + foreach (var fi in fetchItems) { - if (record?.AutoLoadOnDemand ?? true) + // reached end + numItem++; + if (record.PageLimit > 0 && numItem > record.PageLimit) + break; + + // prepare download + Uri loc = null; + if (fi.Type == FetchItemType.SmUrl) + loc = new Uri(fi.Value); + if (fi.Type == FetchItemType.SmId) + loc = BuildUriForRepoSingleSubmodel(baseUri, fi.Value, encryptIds: true); + + if (loc == null) + continue; + + // download (and skip errors) + try { - // side info level 1 - lock (prepSM) - { - prepSM.Add(null, new AasIdentifiableSideInfo() + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + sourceUri: loc, + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => { - IsStub = true, - StubLevel = AasIdentifiableSideInfoLevel.IdOnly, - Id = lr.Reference.Keys[0].Value + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + if (fi.Type == FetchItemType.SmUrl || fi.Type == FetchItemType.SmId) + prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); + } + catch (Exception ex) + { + dlErrors++; + runtimeOptions?.Log?.Error(ex, "Parsing individual fetch element of query."); + } }); - } } - else + catch (Exception ex) { - // no side info => full element - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: BuildUriForSubmodel(baseUri, lr.Reference), - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + dlErrors++; + LogInternally.That.CompletelyIgnoredError(ex); + } + } + + Log.Singleton.Info(StoredPrint.Color.Blue, "Executed GraphQL query. Receiving list of {0} elements, " + + "found {1} errors when individually downloading elements.", fetchItems.Count, dlErrors); + } + + // start auto-load missing Submodels? + if (record?.AutoLoadSubmodels ?? false) + { + var lrs = env.FindAllSubmodelReferences(onlyNotExisting: true).ToList(); + + await Parallel.ForEachAsync(lrs, + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (lr, token) => + { + if (record?.AutoLoadOnDemand ?? true) { - try + // side info level 1 + lock (prepSM) { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - lock (prepSM) + prepSM.Add(null, new AasIdentifiableSideInfo() { - prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); - } + IsStub = true, + StubLevel = AasIdentifiableSideInfoLevel.IdOnly, + Id = lr.Reference.Keys[0].Value + }); } - catch (Exception ex) + } + else + { + // no side info => full element + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + sourceUri: BuildUriForRepoSingleSubmodel(baseUri, lr.Reference), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => + { + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + lock (prepSM) + { + prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing auto-loaded Submodel"); + } + }); + } + }); + } + + // start auto-load missing thumbnails? + if (record?.AutoLoadThumbnails ?? false) + await Parallel.ForEachAsync(env.AllAssetAdministrationShells(), + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (aas, token) => + { + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + sourceUri: BuildUriForRepoAasThumbnail(baseUri, aas.Id), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDone: (ms, contentFn) => { - runtimeOptions?.Log?.Error(ex, "Parsing auto-loaded Submodel"); - } - }); - } - }); + try + { + dynPack.AddThumbnail(aas.Id, ms.ToArray()); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Managing auto-loaded tumbnail"); + } + }); + }); + } - // start auto-load missing thumbnails? - if (record?.AutoLoadThumbnails ?? false) - await Parallel.ForEachAsync(env.AllAssetAdministrationShells(), - new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, - async (aas, token) => { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: BuildUriForAasThumbnail(baseUri, aas.Id), - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => - { - try - { - dynPack.AddThumbnail(aas.Id, ms.ToArray()); - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, "Managing auto-loaded tumbnail"); - } - }); - }); + // + // FINALIZE + // // remove, what is not need if (env.AssetAdministrationShellCount() < 1) @@ -807,14 +1146,15 @@ public class ConnectExtendedRecord public enum BaseTypeEnum { Repository, Registry } public static string[] BaseTypeEnumNames = new[] { "Repository", "Registry" }; - public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; + // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; // public string BaseAddress = "https://eis-data.aas-voyager.com/"; // public string BaseAddress = "http://smt-repo.admin-shell-io.com/"; - // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; + public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; - public BaseTypeEnum BaseType = BaseTypeEnum.Repository; + // public BaseTypeEnum BaseType = BaseTypeEnum.Repository; + public BaseTypeEnum BaseType = BaseTypeEnum.Registry; - public bool GetAllAas; + public bool GetAllAas = true; public bool GetSingleAas; public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; @@ -827,7 +1167,7 @@ public enum BaseTypeEnum { Repository, Registry } public bool GetSingleCD; public string CdId; - public bool ExecuteQuery = true; + public bool ExecuteQuery; // public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG\r\n filter=\r\n or(\r\n str_contains(sm.IdShort, \"Technical\"),\r\n str_contains(sm.IdShort, \"Nameplate\")\r\n )\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG$QL\r\n ( contains(sm.idShort, \"Technical\") and\r\n sme.value ge 100 and\r\n sme.value le 200 )\r\n or\r\n ( contains(sm.idShort, \"Nameplate\") and\r\n contains(sme.idShort,\"ManufacturerName\") and\r\n not(contains(sme.value,\"Phoenix\")))\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; @@ -914,50 +1254,77 @@ public static string BuildLocationFrom( var baseUri = new Uri(record.BaseAddress); - // All AAS? - if (record.GetAllAas) - { - // if a skip has been requested, these AAS need to be loaded, as well - var uri = BuildUriForAllAAS(baseUri, record.PageLimit + record.PageSkip, cursor); - return uri.ToString(); - } + // + // REPO + // - // Single AAS? - if (record.GetSingleAas) + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { - var uri = BuildUriForAAS(baseUri, record.AasId, encryptIds: record.EncryptIds); - return uri.ToString(); - } + // All AAS? + if (record.GetAllAas) + { + // if a skip has been requested, these AAS need to be loaded, as well + var uri = BuildUriForRepoAllAAS(baseUri, record.PageLimit + record.PageSkip, cursor); + return uri.ToString(); + } - // All Submodels? - if (record.GetAllSubmodel) - { - // if a skip has been requested, these AAS need to be loaded, as well - var uri = BuildUriForAllSubmodel(baseUri, record.PageLimit + record.PageSkip, cursor); - return uri.ToString(); - } + // Single AAS? + if (record.GetSingleAas) + { + var uri = BuildUriForRepoSingleAAS(baseUri, record.AasId, encryptIds: record.EncryptIds); + return uri.ToString(); + } - // Single Submodel? - if (record.GetSingleSubmodel) - { - var uri = BuildUriForSubmodel(baseUri, record.SmId, encryptIds: record.EncryptIds); - return uri.ToString(); - } + // All Submodels? + if (record.GetAllSubmodel) + { + // if a skip has been requested, these AAS need to be loaded, as well + var uri = BuildUriForRepoAllSubmodel(baseUri, record.PageLimit + record.PageSkip, cursor); + return uri.ToString(); + } + + // Single Submodel? + if (record.GetSingleSubmodel) + { + var uri = BuildUriForRepoSingleSubmodel(baseUri, record.SmId, encryptIds: record.EncryptIds); + return uri.ToString(); + } + + // Single CD? + if (record.GetSingleCD) + { + var uri = BuildUriForRepoSingleCD(baseUri, record.CdId, encryptIds: record.EncryptIds); + return uri.ToString(); + } + + // Query? + if (record.ExecuteQuery) + { + var uri = BuildUriForRepoQuery(baseUri, record.QueryScript); + return uri.ToString(); + } - // Single CD? - if (record.GetSingleCD) - { - var uri = BuildUriForCD(baseUri, record.CdId, encryptIds: record.EncryptIds); - return uri.ToString(); } - // Query? - if (record.ExecuteQuery) + // + // REGISTRY + // + + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) { - var uri = BuildUriForQuery(baseUri, record.QueryScript); - return uri.ToString(); + // All AAS? + if (record.GetAllAas) + { + // if a skip has been requested, these AAS need to be loaded, as well + var uri = BuildUriForRegistryAllAAS(baseUri, record.PageLimit + record.PageSkip, cursor); + return uri.ToString(); + } } + // + // END + // + // nope return null; } @@ -1019,7 +1386,7 @@ public static async Task PerformConnectExtendedDialogue( uc.ActivateRenderPanel(record, disableScrollArea: false, dialogButtons: AnyUiMessageBoxButton.OK, - extraButtons: new[] { "A", "B" }, + // extraButtons: new[] { "A", "B" }, renderPanel: (uci) => { // create panel diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index 101d703fa..8bfc0bda2 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -81,7 +81,8 @@ private static OpenIdClientInstance.UiLambdaSet GenerateUiLambdaSet(PackCntRunti return res; } - public static async Task HttpGetToMemoryStream( + // TODO: Refactor + public static async Task HttpGetToMemoryStreamOLD( Uri sourceUri, Action lambdaDownloadDone, PackCntRuntimeOptions runtimeOptions = null, @@ -286,6 +287,218 @@ await memStream.WriteAsync(buffer, 0, bytesRead, } } + /// + /// Try get an HTTP ressource by GET. + /// + /// Called also for failed status codes! + /// Any exception might occur outside the HTTP status codes. + public static async Task HttpGetToMemoryStream( + Uri sourceUri, + Action lambdaDownloadDoneOrFail, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null, + bool allowFakeResponses = false) + { + // access + if (sourceUri == null) + return; + + // check for fake answers? + if (allowFakeResponses && _fakeAnswers?.Http != null) + foreach (var fa in _fakeAnswers.Http) + if (fa.Request.Equals(sourceUri.ToString(), StringComparison.InvariantCultureIgnoreCase)) + { + using (var memStream = new MemoryStream()) + using (var writer = new StreamWriter(memStream)) + { + writer.Write(AdminShellUtil.Base64Decode(fa.Response)); + writer.Flush(); + memStream.Flush(); + memStream.Seek(0, SeekOrigin.Begin); + lambdaDownloadDoneOrFail?.Invoke(HttpStatusCode.OK, memStream, "content.json"); + return; + } + } + + // + + // read via HttpClient (uses standard proxies) + var handler = new HttpClientHandler(); + handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; + handler.AllowAutoRedirect = false; + + var client = new HttpClient(handler); + + client.DefaultRequestHeaders.Add("Accept", "application/aas"); + client.BaseAddress = new Uri(sourceUri.GetLeftPart(UriPartial.Authority)); + var requestPath = sourceUri.PathAndQuery; + + // Log + runtimeOptions?.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + + $"and request {requestPath} .. "); + + // Token existing? + var clhttp = containerList as PackageContainerListHttpRestBase; + var oidc = clhttp?.OpenIdClient; + if (oidc == null) + { + runtimeOptions?.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (clhttp != null && OpenIDClient.email != "") + { + clhttp.OpenIdClient = new OpenIdClientInstance(); + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + oidc = clhttp.OpenIdClient; + } + } + if (oidc != null) + { + if (oidc.token != "") + { + runtimeOptions?.Log?.Info($" using existing bearer token."); + client.SetBearerToken(oidc.token); + } + else + { + if (oidc.email != "") + { + runtimeOptions?.Log?.Info($" using existing email token."); + client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); + } + } + } + + bool repeat = true; + + while (repeat) + { + // get response? + var response = await client.GetAsync(requestPath, + HttpCompletionOption.ResponseHeadersRead); + + if (clhttp != null + && response.StatusCode == System.Net.HttpStatusCode.TemporaryRedirect) + { + string redirectUrl = response.Headers.Location.ToString(); + // ReSharper disable once RedundantExplicitArrayCreation + string[] splitResult = redirectUrl.Split(new string[] { "?" }, + StringSplitOptions.RemoveEmptyEntries); + splitResult[0] = splitResult[0].TrimEnd('/'); + + if (splitResult.Length < 1) + { + runtimeOptions?.Log?.Error("TemporaryRedirect, but url split to successful"); + break; + } + + runtimeOptions?.Log?.Info("Redirect to: " + splitResult[0]); + + if (oidc == null) + { + runtimeOptions?.Log?.Info("Creating new OpenIdClient.."); + oidc = new OpenIdClientInstance(); + clhttp.OpenIdClient = oidc; + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + } + + oidc.authServer = splitResult[0]; + + runtimeOptions?.Log?.Info($".. authentication at auth server {oidc.authServer} needed"); + + var response2 = await oidc.RequestTokenAsync(null, + GenerateUiLambdaSet(runtimeOptions)); + if (oidc.keycloak == "" && response2 != null) + oidc.token = response2.AccessToken; + if (oidc.token != "" && oidc.token != null) + client.SetBearerToken(oidc.token); + + repeat = true; + continue; + } + + repeat = false; + + if (response.IsSuccessStatusCode) + { + var contentLength = response.Content.Headers.ContentLength; + var contentFn = response.Content.Headers.ContentDisposition?.FileName; + + // log + runtimeOptions?.Log?.Info($".. response with header-content-len {contentLength} " + + $"and file-name {contentFn} .."); + + var contentStream = await response?.Content?.ReadAsStreamAsync(); + if (contentStream == null) + throw new PackageContainerException( + $"While getting data bytes from {sourceUri.ToString()} via HttpClient " + + $"no data-content was responded!"); + + // create temp file and write to it + var givenFn = sourceUri.ToString(); + if (contentFn != null) + givenFn = contentFn; + runtimeOptions?.Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " + + $"and request {requestPath} .. "); + + using (var memStream = new MemoryStream()) + { + // copy with progress + var bufferSize = 4024; + var deltaSize = 512 * 1024; + var buffer = new byte[bufferSize]; + long totalBytesRead = 0; + long lastBytesRead = 0; + int bytesRead; + + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Starting, + contentLength, totalBytesRead); + + while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, + default(CancellationToken)).ConfigureAwait(false)) != 0) + { + await memStream.WriteAsync(buffer, 0, bytesRead, + default(CancellationToken)).ConfigureAwait(false); + + totalBytesRead += bytesRead; + + if (totalBytesRead > lastBytesRead + deltaSize) + { + runtimeOptions?.Log?.Info($".. downloading to memory stream"); + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Ongoing, + contentLength, totalBytesRead); + lastBytesRead = totalBytesRead; + } + } + + // assume bytes read to be total bytes + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Final, + totalBytesRead, totalBytesRead); + + // log + runtimeOptions?.Log?.Info($".. download done with {totalBytesRead} bytes read!"); + + // execute lambda + memStream.Flush(); + memStream.Seek(0, SeekOrigin.Begin); + lambdaDownloadDoneOrFail?.Invoke(response.StatusCode, memStream, contentFn); + } + } + else + if (response.StatusCode == HttpStatusCode.NotFound) + { + lambdaDownloadDoneOrFail?.Invoke(response.StatusCode, null, null); + } + else + { + Log.Singleton.Error($"DownloadFromSource server gave status code {response.StatusCode}!"); + lambdaDownloadDoneOrFail?.Invoke(response.StatusCode, null, null); + } + } + } + public static async Task HttpPostRequestToMemoryStream( Uri sourceUri, string requestContentType, @@ -347,7 +560,7 @@ await memStream.WriteAsync(buffer, 0, bytesRead, } // prepare request - var requestContent = new StringContent(requestBody, Encoding.UTF8, requestContentType); + var requestContent = new StringContent(requestBody, Encoding.UTF8, requestContentType); // retrieve response bool repeat = true; From af185d102c6107e638a5c6d84b201a06abbd3008 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sat, 12 Oct 2024 23:57:46 +0200 Subject: [PATCH 14/99] * change refactor of side info display --- src/AasxPackageLogic/DispEditHelperBasics.cs | 6 +- .../DispEditHelperEntities.cs | 57 +++- src/AasxPackageLogic/DispEditHelperModules.cs | 30 ++ .../PackageCentral/AasOnDemandEnvironment.cs | 14 + .../PackageContainerHttpRepoSubset.cs | 257 ++++++++++-------- src/AasxPackageLogic/VisualAasxElements.cs | 87 +++--- 6 files changed, 283 insertions(+), 168 deletions(-) diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index bf2aaa09b..3d7fedb3e 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -357,7 +357,8 @@ public void AddKeyValue( int comboBoxMinWidth = -1, int firstColumnWidth = -1, // -1 = Standard int maxLines = -1, - bool keyVertCenter = false) + bool keyVertCenter = false, + bool auxButtonOverride = false) { // draw anyway? if (repo != null && value == null) @@ -386,7 +387,8 @@ public void AddKeyValue( if (auxButtonToolTips != null) intButtonToolTips.AddRange(auxButtonToolTips); - var auxButton = repo != null && intButtonTitles.Count > 0 && auxButtonLambda != null; + var auxButton = auxButtonOverride + || (repo != null && intButtonTitles.Count > 0 && auxButtonLambda != null); // Grid var g = new AnyUiGrid(); diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 3b839b904..6ddf803a9 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -1915,6 +1915,12 @@ public void DisplayOrEditAasEntityAas( } } + // info about sideInfo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.AssetAdministrationShells, aas.GetReference()); + DisplayOrEditEntitySideInfo(env, stack, aas, sideInfo, "AAS", superMenu); + // Referable this.DisplayOrEditEntityReferable( env, stack, @@ -2045,7 +2051,8 @@ public void DisplayOrEditAasEntitySubmodelOrRef( Aas.IAssetAdministrationShell aas, Aas.IReference smref, Action setSmRefNull, - Aas.ISubmodel submodel, + Aas.ISubmodel submodel, + AasIdentifiableSideInfo sideInfo, bool editMode, AnyUiStackPanel stack, bool hintMode = false, bool checkSmt = false, AasxMenu superMenu = null) @@ -2053,7 +2060,7 @@ public void DisplayOrEditAasEntitySubmodelOrRef( // This panel renders first the SubmodelReference and then the Submodel, below if (smref != null) { - this.AddGroup(stack, "SubmodelReference", this.levelColors.MainSection); + this.AddGroup(stack, "SubmodelReference of AAS", this.levelColors.MainSection); Func, AnyUiLambdaActionBase> lambda = (kl) => { @@ -2073,7 +2080,7 @@ public void DisplayOrEditAasEntitySubmodelOrRef( // entities when under AAS (smref) if (editMode && smref != null) { - this.AddGroup(stack, "Editing of entities", this.levelColors.MainSection); + this.AddGroup(stack, "Editing of entities (within specific AAS)", this.levelColors.SubSection); // the event template will help speed up visual updates of the tree var evTemplate = new PackCntChangeEventData() @@ -2114,7 +2121,7 @@ public void DisplayOrEditAasEntitySubmodelOrRef( if (editMode && smref == null && submodel != null) { this.AddGroup( - stack, "Editing of entities (environment's Submodel collection)", + stack, "Editing of entities (within environment)", this.levelColors.MainSection); AddActionPanel(stack, "Submodel:", @@ -2627,7 +2634,11 @@ public void DisplayOrEditAasEntitySubmodelOrRef( } - if (submodel != null) + // info about sideInfo + DisplayOrEditEntitySideInfo(env, stack, submodel, sideInfo, "Submodel", superMenu); + + // Submodel attributes + if (submodel != null) { // Submodel @@ -2742,9 +2753,12 @@ public void DisplayOrEditAasEntitySubmodelOrRef( // ConceptDescription <- via semantic ID ?! // - if (submodel?.SemanticId != null && submodel.SemanticId.Keys.Count > 0) + if (submodel?.SemanticId?.Keys != null && submodel.SemanticId.Keys.Count > 0) { + // cd is easy var cd = env.FindConceptDescriptionByReference(submodel.SemanticId); + + // available? if (cd == null) { this.AddGroup( @@ -2848,8 +2862,9 @@ public void DisplayOrEditAasEntitySubmodelStub( public void DisplayOrEditAasEntityConceptDescription( PackageCentral.PackageCentral packages, Aas.IEnvironment env, - Aas.IReferable parentContainer, Aas.IConceptDescription cd, bool editMode, - ModifyRepo repo, + Aas.IReferable parentContainer, + Aas.IConceptDescription cd, + bool editMode, ModifyRepo repo, AnyUiStackPanel stack, bool embedded = false, bool hintMode = false, bool preventMove = false, AasxMenu superMenu = null) { @@ -2886,6 +2901,13 @@ public void DisplayOrEditAasEntityConceptDescription( label: "Buffer:", superMenu: superMenu); } + // info about sideInfo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.ConceptDescriptions, cd.GetCdReference()); + + DisplayOrEditEntitySideInfo(env, stack, cd, sideInfo, "ConceptDescription", superMenu); + // IReferable Action lambdaRf = (hideExtensions) => { @@ -4165,7 +4187,10 @@ public void DisplayOrEditAasEntitySubmodelElement( if (sme.SemanticId != null && sme.SemanticId.Keys.Count > 0 && !nestedCds) { + // CD var cd = env.FindConceptDescriptionByReference(sme.SemanticId); + + // available if (cd == null) { this.AddGroup( @@ -5323,7 +5348,9 @@ public bool DisplayOrEditCommonEntity( else if (entity is VisualElementAdminShell veaas) { DisplayOrEditAasEntityAas( - packages, veaas.thePackage, veaas.theEnv, veaas.theAas, editMode, stack, hintMode: hintMode, + packages, veaas.thePackage, veaas.theEnv, + veaas.theAas, + editMode, stack, hintMode: hintMode, superMenu: superMenu); } else if (entity is VisualElementAsset veas) @@ -5348,7 +5375,9 @@ public bool DisplayOrEditCommonEntity( { vesmref.theAas.Remove(vesmref.theSubmodelRef); }, - vesmref.theSubmodel, editMode, stack, + vesmref.theSubmodel, + vesmref.theSideInfo, + editMode, stack, hintMode: hintMode, checkSmt: checkSmt, superMenu: superMenu); } @@ -5357,7 +5386,9 @@ public bool DisplayOrEditCommonEntity( DisplayOrEditAasEntitySubmodelOrRef( packages, vesm.theEnv, aas: null, smref: null, setSmRefNull: null, - submodel: vesm.theSubmodel, editMode: editMode, stack: stack, + submodel: vesm.theSubmodel, + sideInfo: vesm.theSideInfo, + editMode: editMode, stack: stack, hintMode: hintMode, checkSmt: checkSmt, superMenu: superMenu); } @@ -5389,7 +5420,9 @@ public bool DisplayOrEditCommonEntity( else if (entity is VisualElementConceptDescription vecd) { DisplayOrEditAasEntityConceptDescription( - packages, vecd.theEnv, null, vecd.theCD, editMode, repo, stack, hintMode: hintMode, + packages, vecd.theEnv, null, + vecd.theCD, + editMode, repo, stack, hintMode: hintMode, superMenu: superMenu, preventMove: cdSortOrder.HasValue && cdSortOrder.Value != VisualElementEnvironmentItem.ConceptDescSortOrder.None); diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 8d2ec4576..da8abf479 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -33,6 +33,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Runtime.Intrinsics.X86; using System.Runtime.Serialization; using System.Text; +using AasxPackageLogic.PackageCentral; namespace AasxPackageLogic { @@ -337,6 +338,35 @@ public void DisplayOrEditEntityReferableContinue( relatedReferable: referable, superMenu: superMenu); } + public void DisplayOrEditEntitySideInfo( + Aas.IEnvironment env, AnyUiStackPanel stack, + Aas.IReferable referable, + AasIdentifiableSideInfo si, + string key, + AasxMenu superMenu = null) + { + // access + if (stack == null || referable == null || si == null) + return; + + this.AddGroup(stack, $"{key} was provided by Endpoint of dynamic fetch environment", + this.levelColors.SubSection); + + AddKeyValue(stack, "StubLevel", "" + si.StubLevel.ToString(), repo: null); + AddKeyValue(stack, "IdShort", "" + si.IdShort, repo: null); + AddKeyValue(stack, "Id", "" + si.Id, repo: null); + AddKeyValue(stack, "Endpoint", "" + si.Endpoint.ToString(), repo: null, + auxButtonTitle: "Copy", + auxButtonLambda: (i) => { + this.context?.ClipboardSet(new AnyUiClipboardData( + text: si.Endpoint.ToString()) + { }); + Log.Singleton.Info(StoredPrint.Color.Blue, "Endpoint copied to clipboard."); + return new AnyUiLambdaActionNone(); + }, + auxButtonOverride: true); + } + // // Extensions // diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index 22d75c217..ad600eef5 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -75,6 +75,20 @@ public int FindSideInfoIndexFromId(string id) return -1; } + + public static AasIdentifiableSideInfo FindSideInfoInListOfIdentifiables( + object listIdf, + Aas.IReference rf) + { + if (rf?.IsValid() == true + && listIdf is OnDemandListIdentifiable smsi) + { + var ndx = smsi.FindSideInfoIndexFromId(rf.GetAsIdentifier()); + if (ndx >= 0) + return smsi.GetSideInfo(ndx); + } + return null; + } } /// diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 77e5faa5f..abbbaa68c 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -466,10 +466,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: lambdaGetLocation?.Invoke(ent), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDoneOrFail: async (code, ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { - await Task.Yield(); - try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); @@ -495,10 +493,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: lambdaGetLocation?.Invoke(ent), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDoneOrFail: async (code, ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { - await Task.Yield(); - try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); @@ -532,10 +528,8 @@ await DownloadListOfIdentifiables( lambdaGetLocation: (loc) => loc, runtimeOptions: runtimeOptions, allowFakeResponses: allowFakeResponses, - lambdaDownloadDoneOrFail: async (code, idf, contentFn, ent) => + lambdaDownloadDoneOrFail: (code, idf, contentFn, ent) => { - await Task.Yield(); - if (code == HttpStatusCode.OK) res = idf; }); @@ -586,11 +580,15 @@ public override async Task LoadFromSourceAsync( // AAS descriptors? if (IsValidUriForRegistryAllAAS(fullItemLocation)) { + // prepare receing the descriptors + dynamic resObj = null; + + // GET await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDoneOrFail: async (code, ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { if (code != HttpStatusCode.OK) return; @@ -602,123 +600,144 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( using (var jsonTextReader = new JsonTextReader(reader)) { JsonSerializer serializer = new JsonSerializer(); - dynamic resObj = serializer.Deserialize(jsonTextReader); - - // do the naive approach first und simply go ahead with dynamic parsing - // these data - foreach (var res in resObj.result) - { - foreach (var ep in res.endpoints) - { - // strictly check IFC - var aasIfc = "" + ep["interface"]; - if (aasIfc != "AAS-1.0") - continue; + resObj = serializer.Deserialize(jsonTextReader); + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); + } + }); - // direct access HREF - var aasUri = new Uri("" + ep.protocolInformation.href); + // have a list of descriptors?! + if (resObj?.result == null) + { + runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + return; + } - // but in order to operate as registry, a list if Submodel endpoints - // is required as well - var smRegged = new List(); - if (HasProperty(res, "submodelDescriptors")) - foreach (var smdesc in res.submodelDescriptors) - { - foreach (var smep in smdesc.endpoints) - { - // strictly check IFC - var smIfc = "" + smep["interface"]; - if (smIfc != "SUBMODEL-1.0") - continue; - - // ok - string href = smep.protocolInformation.href; - if (href.HasContent() == true) - smRegged.Add(new AasIdentifiableSideInfo() - { - IsStub = true, - StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, - Id = smdesc.id, - IdShort = smdesc.idshort, - Endpoint = new Uri(href) - }); - } - } - - // ok - var aas = await DownloadIdentifiableToOK(aasUri, - runtimeOptions, allowFakeResponses); - if (aas != null) + // do the naive approach first und simply go ahead with dynamic parsing + // these data + foreach (var res in resObj.result) + { + foreach (var ep in res.endpoints) + { + // strictly check IFC + var aasIfc = "" + ep["interface"]; + if (aasIfc != "AAS-1.0") + continue; + + // direct access HREF + // var aasUri = new Uri("" + ep.protocolInformation.href); + var aasSi = new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = "" + res.id, + IdShort = "" + res.idShort, + Endpoint = new Uri("" + ep.protocolInformation.href) + }; + + // but in order to operate as registry, a list if Submodel endpoints + // is required as well + var smRegged = new List(); + if (HasProperty(res, "submodelDescriptors")) + foreach (var smdesc in res.submodelDescriptors) + { + foreach (var smep in smdesc.endpoints) + { + // strictly check IFC + var smIfc = "" + smep["interface"]; + if (smIfc != "SUBMODEL-1.0") + continue; + + // ok + string href = smep.protocolInformation.href; + if (href.HasContent() == true) + smRegged.Add(new AasIdentifiableSideInfo() { - // found culprit: Submodels are listed twice (or more) in an AAS - // try filter - var uniqSms = aas.Submodels?.Distinct( - new AdminShellComparers.PredicateEqualityComparer( - (x, y) => x?.Matches(y, MatchMode.Relaxed) == true)) - ?? new List(); - - // make sure the list of Submodel endpoints is the same - // as the AAS expects - if (smRegged.Count != uniqSms.Count()) - { - Log.Singleton.Info(StoredPrint.Color.Blue, - "For downloading AAS at {0}, the number of Submodels " + - "was different to the number of given Submodel endpoints.", - aasUri.ToString()); - - // cycle to next endpoint or next descriptor (more likely) - // continue; - } - - // makes most sense to "recrate" the AAS.Submodels with the side infos - // from the registry - aas.Submodels = null; - foreach (var smrr in smRegged) - aas.AddSubmodelReference(new Aas.Reference( - ReferenceTypes.ModelReference, - (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, smrr.Id) }).ToList())); - - // add this AAS - prepAas.Add(aas, null); - - // check if to add the Submodels - // be prepared to download them - var numRes = await DownloadListOfIdentifiables( - smRegged, - lambdaGetLocation: (si) => si.Endpoint, - runtimeOptions: runtimeOptions, - allowFakeResponses: allowFakeResponses, - lambdaDownloadDoneOrFail: async (code, sm, contentFn, si) => - { - // error ? - if (code != HttpStatusCode.OK) - { - Log.Singleton.Error( - "Could not download Submodel from endpoint given by registry: {0}", - si.Endpoint.ToString()); - - // add as pure side info - si.IsStub = true; - prepSM.Add(null, si); - } - - // no, add with data - si.IsStub = false; - prepSM.Add(sm, si); - }); - } - - // ok - ; - } + IsStub = true, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = smdesc.id, + IdShort = smdesc.idShort, + Endpoint = new Uri(href) + }); } } + + // ok + var aas = await DownloadIdentifiableToOK( + aasSi.Endpoint, runtimeOptions, allowFakeResponses); + if (aas == null) + { + runtimeOptions?.Log?.Error( + "Unable to download AAS via registry. Skipping! Location: {0}", + aasSi.Endpoint.ToString()); + continue; } - catch (Exception ex) + // possible culprit: Submodels are listed twice (or more) in an AAS + // try filter (actually: false alarm, but leave in) + var uniqSms = aas.Submodels?.Distinct( + new AdminShellComparers.PredicateEqualityComparer( + (x, y) => x?.Matches(y, MatchMode.Relaxed) == true)) + ?? new List(); + + // make sure the list of Submodel endpoints is the same (in numbers) + // as the AAS expects + if (smRegged.Count != uniqSms.Count()) { - runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); + Log.Singleton.Info(StoredPrint.Color.Blue, + "For downloading AAS at {0}, the number of Submodels " + + "was different to the number of given Submodel endpoints.", + aasSi.Endpoint.ToString()); + + // cycle to next endpoint or next descriptor (more likely) + // continue; } - }); + + // makes most sense to "recrate" the AAS.Submodels with the side infos + // from the registry + aas.Submodels = null; + foreach (var smrr in smRegged) + aas.AddSubmodelReference(new Aas.Reference( + ReferenceTypes.ModelReference, + (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, smrr.Id) }).ToList())); + + // add this AAS + prepAas.Add(aas, aasSi); + + // check if to add the Submodels + // be prepared to download them + var numRes = await DownloadListOfIdentifiables( + smRegged, + lambdaGetLocation: (si) => si.Endpoint, + runtimeOptions: runtimeOptions, + allowFakeResponses: allowFakeResponses, + lambdaDownloadDoneOrFail: (code, sm, contentFn, si) => + { + // error ? + if (code != HttpStatusCode.OK) + { + Log.Singleton.Error( + "Could not download Submodel from endpoint given by registry: {0}", + si.Endpoint.ToString()); + + // add as pure side info + si.IsStub = true; + prepSM.Add(null, si); + } + + // no, add with data + si.IsStub = false; + prepSM.Add(sm, si); + }); + + // a little debug + runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, + "Retieved AAS (potentially with Submodels) from: {0}", + aasSi.Endpoint.ToString()); + } + } } } diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 0c0c60fdb..df53c0da7 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -851,12 +851,14 @@ public class VisualElementSubmodelRef : VisualElementGeneric, ITaintableIdentifi public Aas.IAssetAdministrationShell theAas = null; public Aas.IReference theSubmodelRef = null; public Aas.ISubmodel theSubmodel = null; + public AasIdentifiableSideInfo theSideInfo = null; public VisualElementSubmodelRef( VisualElementGeneric parent, TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package, Aas.IAssetAdministrationShell aas, - Aas.IReference smr, Aas.ISubmodel sm) + Aas.IReference smr, Aas.ISubmodel sm, + AasIdentifiableSideInfo sideInfo) : base() { this.Parent = parent; @@ -866,6 +868,7 @@ public VisualElementSubmodelRef( this.theAas = aas; this.theSubmodelRef = smr; this.theSubmodel = sm; + this.theSideInfo = sideInfo; this.Background = Options.Curr.GetColor(OptionsInformation.ColorNames.DarkAccentColor); this.Border = Options.Curr.GetColor(OptionsInformation.ColorNames.DarkAccentColor); @@ -924,10 +927,13 @@ public class VisualElementSubmodel : VisualElementGeneric, ITaintableIdentifiabl public AdminShellPackageEnvBase thePackage = null; public Aas.IEnvironment theEnv = null; public Aas.ISubmodel theSubmodel = null; + public AasIdentifiableSideInfo theSideInfo = null; public VisualElementSubmodel( - VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnvBase package, Aas.IEnvironment env, - Aas.ISubmodel sm) + VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnvBase package, + Aas.IEnvironment env, + Aas.ISubmodel sm, + AasIdentifiableSideInfo sideInfo) : base() { this.Parent = parent; @@ -935,6 +941,7 @@ public VisualElementSubmodel( this.thePackage = package; this.theEnv = env; this.theSubmodel = sm; + this.theSideInfo = sideInfo; this.Background = new AnyUiColor(0xffd0d0d0u); this.Border = new AnyUiColor(0xff606060u); @@ -1798,7 +1805,8 @@ public VisualElementGeneric FindSibling(VisualElementGeneric item, bool before = private VisualElementConceptDescription GenerateVisualElementsForSingleCD( TreeViewLineCache cache, Aas.IEnvironment env, - Aas.IConceptDescription cd, VisualElementGeneric parent, + Aas.IConceptDescription cd, + VisualElementGeneric parent, Aas.ISubmodel submodelForCDs) { // access @@ -1820,7 +1828,7 @@ private VisualElementConceptDescription GenerateVisualElementsForSingleCD( continue; // try find in CDs - var vrpCD = env?.FindConceptDescriptionByReference(vlp.ValueId); + var vrpCD = env?.FindConceptDescriptionByReference(vlp.ValueId); if (vrpCD != null && tiCDs?.CdSortOrder == VisualElementEnvironmentItem.ConceptDescSortOrder.BySme) { // nice, add "real" CD @@ -2006,6 +2014,7 @@ private void GenerateInnerElementsForSubmodelOrRef( private VisualElementSubmodelRef GenerateVisuElemForVisualElementSubmodelRef( Aas.IAssetAdministrationShell aas, Aas.ISubmodel sm, + AasIdentifiableSideInfo sideInfo, Aas.IReference smr, VisualElementGeneric parent, TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package = null) @@ -2015,7 +2024,7 @@ private VisualElementSubmodelRef GenerateVisuElemForVisualElementSubmodelRef( return null; // item (even if sm is null) - var tiSm = new VisualElementSubmodelRef(parent, cache, env, package, aas, smr, sm); + var tiSm = new VisualElementSubmodelRef(parent, cache, env, package, aas, smr, sm, sideInfo); tiSm.SetIsExpandedIfNotTouched(OptionExpandMode > 1); if (OptionLazyLoadingFirst && !tiSm.GetExpandedStateFromCache()) @@ -2060,47 +2069,52 @@ private VisualElementAdminShell GenerateVisuElemForAAS( // have submodels? foreach (var smr in aas.AllSubmodels()) { + // find sideInfo + AasIdentifiableSideInfo si = null; + if (smr?.IsValid() == true + && env.Submodels is OnDemandListIdentifiable smsi) + { + var ndx = smsi.FindSideInfoIndexFromId(smr.GetAsIdentifier()); + if (ndx >= 0) + si = smsi.GetSideInfo(ndx); + } + + // find Submodel itself var sm = env.FindSubmodel(smr); + + // check for Submodel is available if (sm == null) { - // check if Submodel with only side info is present - if (smr?.IsValid() == true - && env.Submodels is OnDemandListIdentifiable smsi) + // no SM available, but show a stub? + if (si != null && si.IsStub) { - var ndx = smsi.FindSideInfoIndexFromId(smr.GetAsIdentifier()); - if (ndx >= 0) - { - var si = smsi.GetSideInfo(ndx); - if (si.IsStub) - { + // add stub + var tiSmSi = new VisualElementSubmodelStub(tiAas, cache, package, si); + tiAas.Members.Add(tiSmSi); - // add stub - var tiSmSi = new VisualElementSubmodelStub(tiAas, cache, package, si); - tiAas.Members.Add(tiSmSi); - - // not further here!! - continue; - } - } + // not further here!! + continue; } - // notify user + // no, notify user Log.Singleton.Error("Cannot find some submodel!"); // make reference with NO submodel behind var tiNoSm = new VisualElementSubmodelRef( - tiAas, cache, env, package, aas, smr, sm: null); + tiAas, cache, env, package, aas, smr, sm: null, sideInfo: si); tiAas.Members.Add(tiNoSm); } + else + { + // generate + var tiSm = GenerateVisuElemForVisualElementSubmodelRef( + tiAas.theAas, sm, si, smr, tiAas, cache, env, package); - // generate - var tiSm = GenerateVisuElemForVisualElementSubmodelRef( - tiAas.theAas, sm, smr, tiAas, cache, env, package); - - // add - if (tiSm != null) - tiAas.Members.Add(tiSm); + // add + if (tiSm != null) + tiAas.Members.Add(tiSm); + } } // ok @@ -2544,7 +2558,7 @@ public void AddVisualElementsFromShellEnv( } // Submodel - var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, package, env, sm); + var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, package, env, sm, si); tiSm.SetIsExpandedIfNotTouched(expandMode > 1); tiAllSubmodels.Members.Add(tiSm); @@ -3145,8 +3159,9 @@ public bool UpdateByEvent( continue; // generate + // TODO (MIHO, 2024-10-12): check, if to provide sideInfo var tiSm = GenerateVisuElemForVisualElementSubmodelRef( - parentAas, thisSm, smr, parentVE, cache, + parentAas, thisSm, null, smr, parentVE, cache, data.Container?.Env?.AasEnv, data.Container?.Env); // add @@ -3159,9 +3174,11 @@ public bool UpdateByEvent( (ve is VisualElementEnvironmentItem veei && veei.theItemType == VisualElementEnvironmentItem.ItemType.AllSubmodels))) { + // TODO (MIHO, 2024-10-12): check, if sideInfo would be available var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, data.Container?.Env, - data.Container?.Env?.AasEnv, thisSm); + data.Container?.Env?.AasEnv, thisSm, + sideInfo: null); tiSm.SetIsExpandedIfNotTouched(false); tiAllSubmodels.Members.Add(tiSm); } From e3aaa4d66fbd9e495f74770999f2760301d97f68 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 13 Oct 2024 11:36:52 +0200 Subject: [PATCH 15/99] * showing of side info completely refactored --- .../DispEditHelperEntities.cs | 7 ++--- src/AasxPackageLogic/VisualAasxElements.cs | 26 ++++++------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 6ddf803a9..aabec9662 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -2052,7 +2052,7 @@ public void DisplayOrEditAasEntitySubmodelOrRef( Aas.IReference smref, Action setSmRefNull, Aas.ISubmodel submodel, - AasIdentifiableSideInfo sideInfo, + // AasIdentifiableSideInfo sideInfo, bool editMode, AnyUiStackPanel stack, bool hintMode = false, bool checkSmt = false, AasxMenu superMenu = null) @@ -2635,6 +2635,9 @@ public void DisplayOrEditAasEntitySubmodelOrRef( } // info about sideInfo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.Submodels, submodel.GetReference()); DisplayOrEditEntitySideInfo(env, stack, submodel, sideInfo, "Submodel", superMenu); // Submodel attributes @@ -5376,7 +5379,6 @@ public bool DisplayOrEditCommonEntity( vesmref.theAas.Remove(vesmref.theSubmodelRef); }, vesmref.theSubmodel, - vesmref.theSideInfo, editMode, stack, hintMode: hintMode, checkSmt: checkSmt, superMenu: superMenu); @@ -5387,7 +5389,6 @@ public bool DisplayOrEditCommonEntity( packages, vesm.theEnv, aas: null, smref: null, setSmRefNull: null, submodel: vesm.theSubmodel, - sideInfo: vesm.theSideInfo, editMode: editMode, stack: stack, hintMode: hintMode, checkSmt: checkSmt, superMenu: superMenu); diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index df53c0da7..25b2a48e7 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -851,14 +851,12 @@ public class VisualElementSubmodelRef : VisualElementGeneric, ITaintableIdentifi public Aas.IAssetAdministrationShell theAas = null; public Aas.IReference theSubmodelRef = null; public Aas.ISubmodel theSubmodel = null; - public AasIdentifiableSideInfo theSideInfo = null; public VisualElementSubmodelRef( VisualElementGeneric parent, TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package, Aas.IAssetAdministrationShell aas, - Aas.IReference smr, Aas.ISubmodel sm, - AasIdentifiableSideInfo sideInfo) + Aas.IReference smr, Aas.ISubmodel sm) : base() { this.Parent = parent; @@ -868,7 +866,6 @@ public VisualElementSubmodelRef( this.theAas = aas; this.theSubmodelRef = smr; this.theSubmodel = sm; - this.theSideInfo = sideInfo; this.Background = Options.Curr.GetColor(OptionsInformation.ColorNames.DarkAccentColor); this.Border = Options.Curr.GetColor(OptionsInformation.ColorNames.DarkAccentColor); @@ -927,13 +924,11 @@ public class VisualElementSubmodel : VisualElementGeneric, ITaintableIdentifiabl public AdminShellPackageEnvBase thePackage = null; public Aas.IEnvironment theEnv = null; public Aas.ISubmodel theSubmodel = null; - public AasIdentifiableSideInfo theSideInfo = null; public VisualElementSubmodel( VisualElementGeneric parent, TreeViewLineCache cache, AdminShellPackageEnvBase package, Aas.IEnvironment env, - Aas.ISubmodel sm, - AasIdentifiableSideInfo sideInfo) + Aas.ISubmodel sm) : base() { this.Parent = parent; @@ -941,7 +936,6 @@ public VisualElementSubmodel( this.thePackage = package; this.theEnv = env; this.theSubmodel = sm; - this.theSideInfo = sideInfo; this.Background = new AnyUiColor(0xffd0d0d0u); this.Border = new AnyUiColor(0xff606060u); @@ -2014,7 +2008,6 @@ private void GenerateInnerElementsForSubmodelOrRef( private VisualElementSubmodelRef GenerateVisuElemForVisualElementSubmodelRef( Aas.IAssetAdministrationShell aas, Aas.ISubmodel sm, - AasIdentifiableSideInfo sideInfo, Aas.IReference smr, VisualElementGeneric parent, TreeViewLineCache cache, Aas.IEnvironment env, AdminShellPackageEnvBase package = null) @@ -2024,7 +2017,7 @@ private VisualElementSubmodelRef GenerateVisuElemForVisualElementSubmodelRef( return null; // item (even if sm is null) - var tiSm = new VisualElementSubmodelRef(parent, cache, env, package, aas, smr, sm, sideInfo); + var tiSm = new VisualElementSubmodelRef(parent, cache, env, package, aas, smr, sm); tiSm.SetIsExpandedIfNotTouched(OptionExpandMode > 1); if (OptionLazyLoadingFirst && !tiSm.GetExpandedStateFromCache()) @@ -2102,14 +2095,14 @@ private VisualElementAdminShell GenerateVisuElemForAAS( // make reference with NO submodel behind var tiNoSm = new VisualElementSubmodelRef( - tiAas, cache, env, package, aas, smr, sm: null, sideInfo: si); + tiAas, cache, env, package, aas, smr, sm: null); tiAas.Members.Add(tiNoSm); } else { // generate var tiSm = GenerateVisuElemForVisualElementSubmodelRef( - tiAas.theAas, sm, si, smr, tiAas, cache, env, package); + tiAas.theAas, sm, smr, tiAas, cache, env, package); // add if (tiSm != null) @@ -2558,7 +2551,7 @@ public void AddVisualElementsFromShellEnv( } // Submodel - var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, package, env, sm, si); + var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, package, env, sm); tiSm.SetIsExpandedIfNotTouched(expandMode > 1); tiAllSubmodels.Members.Add(tiSm); @@ -3159,9 +3152,8 @@ public bool UpdateByEvent( continue; // generate - // TODO (MIHO, 2024-10-12): check, if to provide sideInfo var tiSm = GenerateVisuElemForVisualElementSubmodelRef( - parentAas, thisSm, null, smr, parentVE, cache, + parentAas, thisSm, smr, parentVE, cache, data.Container?.Env?.AasEnv, data.Container?.Env); // add @@ -3174,11 +3166,9 @@ public bool UpdateByEvent( (ve is VisualElementEnvironmentItem veei && veei.theItemType == VisualElementEnvironmentItem.ItemType.AllSubmodels))) { - // TODO (MIHO, 2024-10-12): check, if sideInfo would be available var tiSm = new VisualElementSubmodel(tiAllSubmodels, cache, data.Container?.Env, - data.Container?.Env?.AasEnv, thisSm, - sideInfo: null); + data.Container?.Env?.AasEnv, thisSm); tiSm.SetIsExpandedIfNotTouched(false); tiAllSubmodels.Members.Add(tiSm); } From 49eab51ba68c132023fd484ebe7bb936194d76a2 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 14 Oct 2024 11:10:38 +0200 Subject: [PATCH 16/99] * update --- .../options-debug.MIHO.json | 2 +- .../DispEditHelperEntities.cs | 10 ++- src/AasxPackageLogic/DispEditHelperModules.cs | 21 ++++-- .../PackageCentral/AasOnDemandEnvironment.cs | 8 +++ .../AdminShellPackageDynamicFetchEnv.cs | 42 +++++++++--- .../PackageContainerHttpRepoSubset.cs | 66 ++++++++++++------- 6 files changed, 108 insertions(+), 41 deletions(-) diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 9f471745f..8b70988c3 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -102,7 +102,7 @@ "EclassTwoPass": true, "BackupDir": ".\\backup", "BackupFiles": 10, - "MaxParallelOps": 20, + "MaxParallelOps": 1, "AllowFakeResponses": true, "RestServerHost": "localhost", "RestServerPort": "1111", diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index aabec9662..e123c029b 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -1897,7 +1897,8 @@ public void DisplayOrEditAasEntityAas( if (lrs != null) { var ids = lrs.Select((lr) => (lr?.Reference?.IsValid() == true) ? lr.Reference.Keys[0].Value : null).ToList(); - var fetched = await dynPack.TryFetchSpecificIds(ids); + var fetched = await dynPack.TryFetchSpecificIds(ids, + useParallel: Options.Curr.MaxParallelOps > 1); if (fetched) return new AnyUiLambdaActionRedrawAllElements(nextFocus: aas); } @@ -2798,7 +2799,8 @@ public void DisplayOrEditAasEntitySubmodelOrRef( // public void DisplayOrEditAasEntitySubmodelStub( - PackageCentral.PackageCentral packages, AdminShellPackageEnvBase packEnv, + PackageCentral.PackageCentral packages, + AdminShellPackageEnvBase packEnv, Aas.IAssetAdministrationShell aas, Aas.IReference smref, Action setSmRefNull, @@ -2823,6 +2825,10 @@ public void DisplayOrEditAasEntitySubmodelStub( } else { + // infos + DisplayOrEditEntitySideInfo(packEnv?.AasEnv, stack, aas, sideInfo, "Submodel", superMenu); + + // actions AddActionPanel(stack, "Action:", repo: repo, superMenu: superMenu, diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index da8abf479..5b0a27dce 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -346,22 +346,29 @@ public void DisplayOrEditEntitySideInfo( AasxMenu superMenu = null) { // access - if (stack == null || referable == null || si == null) + if (stack == null || si == null) return; - this.AddGroup(stack, $"{key} was provided by Endpoint of dynamic fetch environment", + this.AddGroup(stack, $"{key} is provided by Endpoint of dynamic fetch environment", this.levelColors.SubSection); AddKeyValue(stack, "StubLevel", "" + si.StubLevel.ToString(), repo: null); AddKeyValue(stack, "IdShort", "" + si.IdShort, repo: null); AddKeyValue(stack, "Id", "" + si.Id, repo: null); - AddKeyValue(stack, "Endpoint", "" + si.Endpoint.ToString(), repo: null, + AddKeyValue(stack, "Endpoint", "" + si.Endpoint?.ToString(), repo: null, auxButtonTitle: "Copy", auxButtonLambda: (i) => { - this.context?.ClipboardSet(new AnyUiClipboardData( - text: si.Endpoint.ToString()) - { }); - Log.Singleton.Info(StoredPrint.Color.Blue, "Endpoint copied to clipboard."); + if (si.Endpoint == null) + { + Log.Singleton.Error("No endpoint data available"); + } + else + { + this.context?.ClipboardSet(new AnyUiClipboardData( + text: si.Endpoint.ToString()) + { }); + Log.Singleton.Info(StoredPrint.Color.Blue, "Endpoint copied to clipboard."); + } return new AnyUiLambdaActionNone(); }, auxButtonOverride: true); diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index ad600eef5..7cb8c24b3 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -76,6 +76,14 @@ public int FindSideInfoIndexFromId(string id) return -1; } + public AasIdentifiableSideInfo FindSideInfoFromId(string id) + { + var ndx = FindSideInfoIndexFromId(id); + if (ndx >= 0) + return GetSideInfo(ndx); + return null; + } + public static AasIdentifiableSideInfo FindSideInfoInListOfIdentifiables( object listIdf, Aas.IReference rf) diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index c0d2a73ea..754d278b9 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -125,30 +125,49 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( var smndx = sms?.FindSideInfoIndexFromId(id); if (smndx.HasValue && smndx.Value >= 0) { + // side info + var si = sms.GetSideInfo(smndx.Value); + // directly use id to fetch Identifiable Aas.IIdentifiable res = null; // build the location - var loc = PackageContainerHttpRepoSubset.BuildUriForRepoSingleSubmodel(_defaultRepoBaseUri, id); + var loc = (si.StubLevel >= AasIdentifiableSideInfoLevel.IdWithEndpoint && si.Endpoint != null) + ? si.Endpoint + : PackageContainerHttpRepoSubset.BuildUriForRepoSingleSubmodel(_defaultRepoBaseUri, id); if (loc == null) return null; - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( sourceUri: loc, allowFakeResponses: _runtimeOptions?.AllowFakeResponses ?? false, runtimeOptions: _runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + // fail!! + if (code != HttpStatusCode.OK) + { + _runtimeOptions?.Log?.Error($"Error while downloading on-demand loaded Submodel. " + + $"Endpoint was expected to be available! Status: {(int)code} {code}. " + + $"Location: {loc.ToString()}"); + return; + } + try { // load? var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var sm = Jsonization.Deserialize.SubmodelFrom(node); - // replace, side info will go null + // replace, side info need to be preserved! lock (_aasEnv.Submodels) { + // data sms[smndx.Value] = sm; + + // side info + si.IsStub = false; + sms.SetSideInfo(smndx.Value, si); } // ok @@ -171,10 +190,12 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( return null; } - public async Task TryFetchSpecificIds(IEnumerable ids) + public async Task TryFetchSpecificIds( + IEnumerable ids, + bool useParallel = true) { var someFetched = false; - if (true) + if (useParallel) { // parallel await Parallel.ForEachAsync( @@ -233,7 +254,7 @@ public async Task TryFetchAllMissingIdentifiables( protected async Task TrySaveAllTaintedIdentifiablesOf( object listOfIdf, - Func lambdaBuildRessource, + Func lambdaBuildRessourceForNoEndpoint, bool clearTaintedFlags = true) where T : Aas.IIdentifiable { var list = listOfIdf as OnDemandListIdentifiable; @@ -260,8 +281,11 @@ protected async Task TrySaveAllTaintedIdentifiablesOf( if (tidf?.TaintedData != null && tidf.TaintedData.Tainted == null) continue; - // try save, need a REST ressource - var uri = lambdaBuildRessource(_defaultRepoBaseUri, idf.Id); + // try save, need a REST ressource. Either use the existing endpoint + // or build the uri. + var uri = (si.StubLevel >= AasIdentifiableSideInfoLevel.IdWithEndpoint && si.Endpoint != null) + ? si.Endpoint + : lambdaBuildRessourceForNoEndpoint(_defaultRepoBaseUri, idf.Id); if (uri == null) continue; diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index abbbaa68c..ab82f1e1e 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -707,30 +707,52 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( prepAas.Add(aas, aasSi); // check if to add the Submodels - // be prepared to download them - var numRes = await DownloadListOfIdentifiables( - smRegged, - lambdaGetLocation: (si) => si.Endpoint, - runtimeOptions: runtimeOptions, - allowFakeResponses: allowFakeResponses, - lambdaDownloadDoneOrFail: (code, sm, contentFn, si) => - { - // error ? - if (code != HttpStatusCode.OK) + if (!record.AutoLoadOnDemand) + { + // be prepared to download them + var numRes = await DownloadListOfIdentifiables( + smRegged, + lambdaGetLocation: (si) => si.Endpoint, + runtimeOptions: runtimeOptions, + allowFakeResponses: allowFakeResponses, + lambdaDownloadDoneOrFail: (code, sm, contentFn, si) => { - Log.Singleton.Error( - "Could not download Submodel from endpoint given by registry: {0}", - si.Endpoint.ToString()); + // error ? + if (code != HttpStatusCode.OK) + { + Log.Singleton.Error( + "Could not download Submodel from endpoint given by registry: {0}", + si.Endpoint.ToString()); - // add as pure side info - si.IsStub = true; - prepSM.Add(null, si); - } + // add as pure side info + si.IsStub = true; + prepSM.Add(null, si); + } - // no, add with data - si.IsStub = false; - prepSM.Add(sm, si); - }); + // no, add with data + si.IsStub = false; + prepSM.Add(sm, si); + }); + } + else + { + foreach (var si in smRegged) + { + // valid Id is required + if (si?.Id?.HasContent() != true) + continue; + + // for the Submodels add Identifiables with null content, but side infos + var siEx = prepSM.FindSideInfoFromId(si.Id); + if (siEx != null) + // already existing! + continue; + + // need to do + si.IsStub = true; + prepSM.Add(null, si); + } + } // a little debug runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, @@ -745,7 +767,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // REPO // - if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { // start with a list of AAS or Submodels (very similar) var isAllAAS = IsValidUriForRepoAllAAS(fullItemLocation); From 6725c43ceadb797492edc72bd66cc5abfaed7430 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 20 Oct 2024 13:09:17 +0200 Subject: [PATCH 17/99] * update (before change) --- .../Extensions/ExtendEnvironment.cs | 84 +- .../Extensions/LocatedReference.cs | 22 + src/AasxPackageExplorer/MainWindow.xaml.cs | 2 +- src/AasxPackageExplorer/debug.MIHO.script | 6 +- .../options-debug.MIHO.json | 1 + src/AasxPackageLogic/ExplorerMenuFactory.cs | 8 +- .../MainWindowAnyUiDialogs.cs | 86 +- .../PackageContainerHttpRepoSubset.cs | 734 +++++++++++++----- 8 files changed, 717 insertions(+), 226 deletions(-) diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index e4586ef1d..1c5af5ef3 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -8,6 +8,7 @@ This source code may use other Open Source software components (see LICENSE.txt) */ using AdminShellNS; using AdminShellNS.Extensions; +using JetBrains.Annotations; using Newtonsoft.Json; using System; using System.Collections; @@ -982,6 +983,27 @@ public static IEnumerable FindAllSubmodelReferences( yield return lr; } + /// + /// Warning: very inefficient! + /// + public static IEnumerable FindAllSemanticIdsForSubmodel( + this AasCore.Aas3_0.IEnvironment environment, + ISubmodel sm) + { + // unique set of references + var refs = new List(); + sm?.RecurseOnSubmodelElements(null, (state, parents, sme) => + { + if (sme.SemanticId != null) + refs.AddIfNew(new LocatedReference() { Identifiable = sm, Reference = sme.SemanticId }); + + // recurse + return true; + }); + + return refs; + } + /// /// Warning: very inefficient! /// @@ -994,17 +1016,65 @@ public static IEnumerable FindAllSemanticIdsForAas( foreach (var smr in aas?.AllSubmodels()) { var sm = environment.FindSubmodel(smr); - sm?.RecurseOnSubmodelElements(null, (state, parents, sme) => - { - if (sme.SemanticId != null) - refs.AddIfNew(new LocatedReference() { Identifiable = sm, Reference = sme.SemanticId }); + refs.AddRange(environment.FindAllSemanticIdsForSubmodel(sm)); + } + + return refs; + } - // recurse - return true; + public static IEnumerable FindAllSubmodelReferencesForAAS( + this AasCore.Aas3_0.IEnvironment environment, + IAssetAdministrationShell aas) + { + // set of references + var refs = new List(); + foreach (var smr in aas?.AllSubmodels()) + { + var sm = environment.FindSubmodel(smr); + if (sm != null) + refs.AddIfNew(new LocatedReference() { Identifiable = sm, Reference = smr }); + } + return refs; + } + + /// + /// Warning: very inefficient! + /// + public static IEnumerable FindAllIdentifiableReferencesFor( + this AasCore.Aas3_0.IEnvironment env, + IIdentifiable idf, + bool makeDistint = true) + { + // set of references + var refs = new List(); + + if (idf is IAssetAdministrationShell aas) + { + refs.AddRange(env.FindAllSubmodelReferencesForAAS(aas)); + refs.AddRange(env.FindAllSemanticIdsForAas(aas)); + } + + if (idf is ISubmodel sm) + { + refs.AddRange(env.FindAllSemanticIdsForSubmodel(sm)); + } + + if (idf is IConceptDescription cd) + { + refs.Add(new LocatedReference() { + Identifiable = cd, + Reference = cd.GetReference() }); } - return refs; + // more distinct? + if (makeDistint) + { + var refs2 = refs.Distinct(new LocatedReferenceComparer()); + return refs2; + } + else + return refs; } /// diff --git a/src/AasxCsharpLibrary/Extensions/LocatedReference.cs b/src/AasxCsharpLibrary/Extensions/LocatedReference.cs index 4fcc6cc97..2b7fe285e 100644 --- a/src/AasxCsharpLibrary/Extensions/LocatedReference.cs +++ b/src/AasxCsharpLibrary/Extensions/LocatedReference.cs @@ -25,6 +25,28 @@ public LocatedReference(IIdentifiable identifiable, IReference reference) } } + /// + /// This comparer takes a shortcut and does ONLY compare the object references, but + /// NOT the complicated AAS references. In a LocatedReference they are assumed + /// to be equivalent to each other!! + /// + public class LocatedReferenceComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(LocatedReference x, LocatedReference y) + { + if (x == null && y == null) + return true; + if (x == null || y == null) + return false; + return (x.Identifiable == y.Identifiable); + } + + int IEqualityComparer.GetHashCode(LocatedReference obj) + { + return obj?.Identifiable?.GetHashCode() ?? 0; + } + } + public static class LocatedReferenceExtensions { public static void AddIfNew(this IList refs, LocatedReference newLR) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index ef474b26d..ffce095df 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -1290,7 +1290,7 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) ; } - AasxIntegrationBaseWpf.CountryFlagWpf.LoadImage(); + // AasxIntegrationBaseWpf.CountryFlagWpf.LoadImage(); } private void ToolFindReplace_ResultSelected(AasxSearchUtil.SearchResultItem resultItem) diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 74750c9c5..b20845984 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,8 +9,10 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -Tool("connectextended"); -Select("Submodel", "First"); +// Tool("connectextended"); +Select("AAS", "First"); +Tool("apiuploadassistant"); +// Select("Submodel", "First"); // Select("Submodel", "Next"); // Tool("exportsmtasciidoc", "File", "C:\Users\homi0002\Desktop\tmp\new.zip", "ExportHtml", "true", "ExportPdf", "false", "AntoraStyle", "false", "ViewResult", "true"); // Select("Plugin", "First"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 8b70988c3..2de47bfad 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -52,6 +52,7 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\MTP\\test-data-SMT_MTP.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\00_FestoDemoBox-Module-2-Kopie.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work_spiel.aasx", + "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v02.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SAMSON_TROVIS_3730-3_Vorlage_Demo_Achema.aasx", diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index c2d9181a3..91e30109d 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -52,8 +52,7 @@ public static AasxMenu CreateMainMenu() help: "Open existing AASX package.", args: new AasxMenuListOfArgDefs() .Add("File", "Source filename including a path and extension.")) - .AddWpfBlazor(name: "ConnectIntegrated", header: "Connect …", inputGesture: "Ctrl+Shift+I") - .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …") + .AddWpfBlazor(name: "ConnectIntegrated", header: "Connect …", inputGesture: "Ctrl+Shift+I") .AddWpfBlazor(name: "Save", header: "_Save", inputGesture: "Ctrl+S") .AddWpfBlazor(name: "SaveAs", header: "_Save as …", help: "Saves current package to given file name and typr", @@ -107,6 +106,11 @@ public static AasxMenu CreateMainMenu() .AddWpfBlazor(name: "ConnectOpcUa", header: "Connect via OPC-UA …") .AddWpfBlazor(name: "ConnectRest", header: "Connect via REST …", inputGesture: "F6")) .AddSeparator() + .AddMenu(header: "API for Registry and Repository …", childs: (new AasxMenu()) + .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …") + .AddWpfBlazor(name: "ApiUploadAssistant", header: "Upload assistant …") + ) + .AddSeparator() .AddMenu(header: "AASX File Repository …", childs: (new AasxMenu()) .AddWpfBlazor(name: "FileRepoNew", header: "New (local) repository …", help: "Create new (empty) file repository.") diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 350464818..b4d3e7022 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -522,56 +522,60 @@ await val.PerformDialogue(ticket, DisplayContext, doCheckTainted: true, doFetchGoNext: false, doFetchExec: true); + } + catch (Exception ex) + { + LogErrorToTicket(ticket, ex, "when performing extended connect"); + } + } - // success will trigger redraw independently, therefore always do nothing - - //var record = new ConnectExtendedRecord(); - - //var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( - // ticket, DisplayContext, - // "Connect AAS repositories and registries", - // record); - - //if (!uiRes) - // return; - - //var location = PackageContainerHttpRepoSubset.BuildLocationFrom(record); - //if (location == null) - //{ - // LogErrorToTicket(ticket, "Error building location from query selection. Aborting."); - // return; - //} + if (cmd == "apiuploadassistant") + { + // start + ticket.StartExec(); - //// more details into container options - //var containerOptions = new PackageContainerHttpRepoSubset. - // PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), - // record); + //do + try + { + // work in one environment + var env = PackageCentral.Main?.AasEnv; + if (env == null) + { + LogErrorToTicket(ticket, "No main package environment available. Aborting!"); + return; + } - //// load - //Log.Singleton.Info($"For extended connect, loading " + - // $"from {location} into container"); + // make a list of Identifiables + var idfs = new List(); + foreach (var idf in ticket.SelectedDereferencedMainDataObjects + .Where((o) => o is Aas.IIdentifiable) + .Cast()) + { + idfs.AddRange(env.FindAllIdentifiableReferencesFor(idf, makeDistint: false) + .Select((lr) => lr.Identifiable)); + } - //var container = await PackageContainerFactory.GuessAndCreateForAsync( - // PackageCentral, - // location, - // location, - // overrideLoadResident: true, - // containerOptions: containerOptions, - // runtimeOptions: PackageCentral.CentralRuntimeOptions); + // suffice? + if (idfs.Count < 1) + { + LogErrorToTicket(ticket, "No specific AAS, Submodel, ConceptDescription in " + + "main package environment selected. Aborting!"); + return; + } - //if (container == null) - // Log.Singleton.Error($"Failed to load from {location}"); - //else - // MainWindow.UiLoadPackageWithNew(PackageCentral.MainItem, - // takeOverContainer: container, onlyAuxiliary: false, indexItems: true, - // storeFnToLRU: location, - // preserveEditMode: true) ; + // make distinct (default comparer should work on list of Identifiables) + var idfs2 = idfs.Distinct(); - //Log.Singleton.Info($"Successfully loaded {location}"); + // call assistant + await PackageContainerHttpRepoSubset.PerformUploadAssistant( + ticket, DisplayContext, + "Upload current to Registry or Repository", + PackageCentral.Main, + idfs2); } catch (Exception ex) { - LogErrorToTicket(ticket, ex, "when performing extended connect"); + LogErrorToTicket(ticket, ex, "when performing api upload assistant"); } } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index ab82f1e1e..62bfa7d84 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -208,6 +208,20 @@ public static bool IsValidUriForRegistryAllAAS(string location) return m.Success; } + public static bool IsValidUriForRegistrySingleAAS(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/shell-descriptors/([^?]{1,99})", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + + public static bool IsValidUriForRegistryAasByAssetId(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/lookup/shells/{0,1}\?(.*)assetId=([-A-Za-z0-9_]{1,99})", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + // // ALL // @@ -220,14 +234,16 @@ public static bool IsValidUriAnyMatch(string location) || IsValidUriForRepoSingleSubmodel(location) || IsValidUriForRepoSingleCD(location) || IsValidUriForRepoQuery(location) - || IsValidUriForRegistryAllAAS(location); + || IsValidUriForRegistryAllAAS(location) + || IsValidUriForRegistrySingleAAS(location) + || IsValidUriForRegistryAasByAssetId(location); } public static Uri GetBaseUri(string location) { // try an explicit search for known parts of ressources // (preserves scheme, host and leading pathes) - var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/conceptdescription)"); + var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/conceptdescription|/lookup)"); if (m.Success) return new Uri(m.Groups[1].ToString() + "/"); @@ -393,6 +409,29 @@ public static Uri BuildUriForRegistryAllAAS(Uri baseUri, int pageLimit = 100, st return uri.Uri; } + public static Uri BuildUriForRegistrySingleAAS(Uri baseUri, string id, bool encryptIds = true) + { + // access + if (id?.HasContent() != true) + return null; + + // try combine + var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"shell-descriptors/{smidenc}"); + } + + public static Uri BuildUriForRegistryAasByAssetLink(Uri baseUri, string id, bool encryptIds = true) + { + // access + if (id?.HasContent() != true) + return null; + + // try combine + var assenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"lookup/shells?assetId={assenc}"); + } + + // // ALL // @@ -468,6 +507,14 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( runtimeOptions: runtimeOptions, lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + // not OK? + if (code != HttpStatusCode.OK) + { + lambdaDownloadDoneOrFail?.Invoke(code, default(T), null, ent); + return; + } + + // go on try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); @@ -482,7 +529,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } }); } - } else + } + else { await Parallel.ForEachAsync(entities, new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, @@ -495,6 +543,14 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( runtimeOptions: runtimeOptions, lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + // not OK? + if (code != HttpStatusCode.OK) + { + lambdaDownloadDoneOrFail?.Invoke(code, default(T), null, ent); + return; + } + + // go on try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); @@ -537,6 +593,202 @@ await DownloadListOfIdentifiables( return res; } + /// + /// Can download arbitrary dynamic entity. + /// + /// Either dynamic object or null + protected async Task DownloadEntityToDynamicObject( + Uri uri, + PackCntRuntimeOptions runtimeOptions = null, + bool allowFakeResponses = false) + { + // prepare receing the descriptors + dynamic resObj = null; + + // GET + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: uri, + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: (code, ms, contentFn) => + { + if (code != HttpStatusCode.OK) + return; + + try + { + // try working with dynamic objects + using (StreamReader reader = new StreamReader(ms, System.Text.Encoding.UTF8, true)) + using (var jsonTextReader = new JsonTextReader(reader)) + { + JsonSerializer serializer = new JsonSerializer(); + resObj = serializer.Deserialize(jsonTextReader); + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); + } + }); + + return resObj; + } + + private async Task FromRegistryGetAasAndSubmodels( + OnDemandListIdentifiable prepAas, + OnDemandListIdentifiable prepSM, + ConnectExtendedRecord record, + PackCntRuntimeOptions runtimeOptions, + bool allowFakeResponses, + dynamic aasDescriptor) + { + // access + if (record == null) + return false; + + foreach (var ep in aasDescriptor.endpoints) + { + // strictly check IFC + var aasIfc = "" + ep["interface"]; + if (aasIfc != "AAS-1.0") + continue; + + // direct access HREF + // var aasUri = new Uri("" + ep.protocolInformation.href); + var aasSi = new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = "" + aasDescriptor.id, + IdShort = "" + aasDescriptor.idShort, + Endpoint = new Uri("" + ep.protocolInformation.href) + }; + + // but in order to operate as registry, a list if Submodel endpoints + // is required as well + var smRegged = new List(); + if (HasProperty(aasDescriptor, "submodelDescriptors")) + foreach (var smdesc in aasDescriptor.submodelDescriptors) + { + foreach (var smep in smdesc.endpoints) + { + // strictly check IFC + var smIfc = "" + smep["interface"]; + if (smIfc != "SUBMODEL-1.0") + continue; + + // ok + string href = smep.protocolInformation.href; + if (href.HasContent() == true) + smRegged.Add(new AasIdentifiableSideInfo() + { + IsStub = true, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = smdesc.id, + IdShort = smdesc.idShort, + Endpoint = new Uri(href) + }); + } + } + + // ok + var aas = await DownloadIdentifiableToOK( + aasSi.Endpoint, runtimeOptions, allowFakeResponses); + if (aas == null) + { + runtimeOptions?.Log?.Error( + "Unable to download AAS via registry. Skipping! Location: {0}", + aasSi.Endpoint.ToString()); + continue; + } + // possible culprit: Submodels are listed twice (or more) in an AAS + // try filter (actually: false alarm, but leave in) + var uniqSms = aas.Submodels?.Distinct( + new AdminShellComparers.PredicateEqualityComparer( + (x, y) => x?.Matches(y, MatchMode.Relaxed) == true)) + ?? new List(); + + // make sure the list of Submodel endpoints is the same (in numbers) + // as the AAS expects + if (smRegged.Count != uniqSms.Count()) + { + Log.Singleton.Info(StoredPrint.Color.Blue, + "For downloading AAS at {0}, the number of Submodels " + + "was different to the number of given Submodel endpoints.", + aasSi.Endpoint.ToString()); + + // cycle to next endpoint or next descriptor (more likely) + // continue; + } + + // makes most sense to "recrate" the AAS.Submodels with the side infos + // from the registry + aas.Submodels = null; + foreach (var smrr in smRegged) + aas.AddSubmodelReference(new Aas.Reference( + ReferenceTypes.ModelReference, + (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, smrr.Id) }).ToList())); + + // add this AAS + prepAas?.Add(aas, aasSi); + + // check if to add the Submodels + if (!record.AutoLoadOnDemand) + { + // be prepared to download them + var numRes = await DownloadListOfIdentifiables( + smRegged, + lambdaGetLocation: (si) => si.Endpoint, + runtimeOptions: runtimeOptions, + allowFakeResponses: allowFakeResponses, + lambdaDownloadDoneOrFail: (code, sm, contentFn, si) => + { + // error ? + if (code != HttpStatusCode.OK) + { + Log.Singleton.Error( + "Could not download Submodel from endpoint given by registry: {0}", + si.Endpoint.ToString()); + + // add as pure side info + si.IsStub = true; + prepSM.Add(null, si); + } + + // no, add with data + si.IsStub = false; + prepSM?.Add(sm, si); + }); + } + else + { + foreach (var si in smRegged) + { + // valid Id is required + if (si?.Id?.HasContent() != true) + continue; + + // for the Submodels add Identifiables with null content, but side infos + var siEx = prepSM.FindSideInfoFromId(si.Id); + if (siEx != null) + // already existing! + continue; + + // need to do + si.IsStub = true; + prepSM.Add(null, si); + } + } + + // a little debug + runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, + "Retrieved AAS (potentially with Submodels) from: {0}", + aasSi.Endpoint.ToString()); + } + + return true; + } + public override async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, @@ -575,13 +827,61 @@ public override async Task LoadFromSourceAsync( // REGISTRY // + var operationFound = false; + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) { // AAS descriptors? - if (IsValidUriForRegistryAllAAS(fullItemLocation)) + var isAllAas = IsValidUriForRegistryAllAAS(fullItemLocation); + var isAasByAssetId = IsValidUriForRegistryAasByAssetId(fullItemLocation); + if (isAllAas || isAasByAssetId) { + // ok + operationFound = true; + // prepare receing the descriptors - dynamic resObj = null; + var resObj = await DownloadEntityToDynamicObject( + new Uri(fullItemLocation), runtimeOptions, allowFakeResponses); + + // Note: the result format for GetAllAAS and GetAllAssetAdministrationShellIdsByAssetLink + // is diffeent! + var arrObj = resObj; + if (isAllAas) + arrObj = resObj?.result; + + // have a list of descriptors?! + if (resObj == null) + { + runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + return; + } + + // Have a list of ids. Decompose into single id. + // Note: Parallel makes no sense, ideally only 1 result (is per AssetId)!! + foreach (var res in resObj) + { + // in res, have only an id. Get the descriptor + var id = "" + res; + var singleDesc = await DownloadEntityToDynamicObject( + BuildUriForRegistrySingleAAS(baseUri, id, encryptIds: true), + runtimeOptions, allowFakeResponses); + if (singleDesc == null || !HasProperty(singleDesc, "endpoints")) + continue; + + // refer to dedicated function + await FromRegistryGetAasAndSubmodels( + prepAas, prepSM, record, runtimeOptions, allowFakeResponses, singleDesc); + } + } + + // start with single AAS? + if (IsValidUriForRegistrySingleAAS(fullItemLocation)) + { + // ok + operationFound = true; + + // prepare receing the descriptors + dynamic aasDesc = null; // GET await PackageHttpDownloadUtil.HttpGetToMemoryStream( @@ -600,7 +900,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( using (var jsonTextReader = new JsonTextReader(reader)) { JsonSerializer serializer = new JsonSerializer(); - resObj = serializer.Deserialize(jsonTextReader); + aasDesc = serializer.Deserialize(jsonTextReader); } } catch (Exception ex) @@ -609,156 +909,20 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } }); - // have a list of descriptors?! - if (resObj?.result == null) + // have a descriptors with some members + if (aasDesc?.id == null) { - runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + runtimeOptions?.Log?.Error("Registry did not return a valid AAS descriptor! Aborting."); return; } - // do the naive approach first und simply go ahead with dynamic parsing - // these data - foreach (var res in resObj.result) + // refer to dedicated function + var res = await FromRegistryGetAasAndSubmodels( + prepAas, prepSM, record, runtimeOptions, allowFakeResponses, aasDesc); + if (!res) { - foreach (var ep in res.endpoints) - { - // strictly check IFC - var aasIfc = "" + ep["interface"]; - if (aasIfc != "AAS-1.0") - continue; - - // direct access HREF - // var aasUri = new Uri("" + ep.protocolInformation.href); - var aasSi = new AasIdentifiableSideInfo() - { - IsStub = false, - StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, - Id = "" + res.id, - IdShort = "" + res.idShort, - Endpoint = new Uri("" + ep.protocolInformation.href) - }; - - // but in order to operate as registry, a list if Submodel endpoints - // is required as well - var smRegged = new List(); - if (HasProperty(res, "submodelDescriptors")) - foreach (var smdesc in res.submodelDescriptors) - { - foreach (var smep in smdesc.endpoints) - { - // strictly check IFC - var smIfc = "" + smep["interface"]; - if (smIfc != "SUBMODEL-1.0") - continue; - - // ok - string href = smep.protocolInformation.href; - if (href.HasContent() == true) - smRegged.Add(new AasIdentifiableSideInfo() - { - IsStub = true, - StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, - Id = smdesc.id, - IdShort = smdesc.idShort, - Endpoint = new Uri(href) - }); - } - } - - // ok - var aas = await DownloadIdentifiableToOK( - aasSi.Endpoint, runtimeOptions, allowFakeResponses); - if (aas == null) - { - runtimeOptions?.Log?.Error( - "Unable to download AAS via registry. Skipping! Location: {0}", - aasSi.Endpoint.ToString()); - continue; - } - // possible culprit: Submodels are listed twice (or more) in an AAS - // try filter (actually: false alarm, but leave in) - var uniqSms = aas.Submodels?.Distinct( - new AdminShellComparers.PredicateEqualityComparer( - (x, y) => x?.Matches(y, MatchMode.Relaxed) == true)) - ?? new List(); - - // make sure the list of Submodel endpoints is the same (in numbers) - // as the AAS expects - if (smRegged.Count != uniqSms.Count()) - { - Log.Singleton.Info(StoredPrint.Color.Blue, - "For downloading AAS at {0}, the number of Submodels " + - "was different to the number of given Submodel endpoints.", - aasSi.Endpoint.ToString()); - - // cycle to next endpoint or next descriptor (more likely) - // continue; - } - - // makes most sense to "recrate" the AAS.Submodels with the side infos - // from the registry - aas.Submodels = null; - foreach (var smrr in smRegged) - aas.AddSubmodelReference(new Aas.Reference( - ReferenceTypes.ModelReference, - (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, smrr.Id) }).ToList())); - - // add this AAS - prepAas.Add(aas, aasSi); - - // check if to add the Submodels - if (!record.AutoLoadOnDemand) - { - // be prepared to download them - var numRes = await DownloadListOfIdentifiables( - smRegged, - lambdaGetLocation: (si) => si.Endpoint, - runtimeOptions: runtimeOptions, - allowFakeResponses: allowFakeResponses, - lambdaDownloadDoneOrFail: (code, sm, contentFn, si) => - { - // error ? - if (code != HttpStatusCode.OK) - { - Log.Singleton.Error( - "Could not download Submodel from endpoint given by registry: {0}", - si.Endpoint.ToString()); - - // add as pure side info - si.IsStub = true; - prepSM.Add(null, si); - } - - // no, add with data - si.IsStub = false; - prepSM.Add(sm, si); - }); - } - else - { - foreach (var si in smRegged) - { - // valid Id is required - if (si?.Id?.HasContent() != true) - continue; - - // for the Submodels add Identifiables with null content, but side infos - var siEx = prepSM.FindSideInfoFromId(si.Id); - if (siEx != null) - // already existing! - continue; - - // need to do - si.IsStub = true; - prepSM.Add(null, si); - } - } - - // a little debug - runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, - "Retieved AAS (potentially with Submodels) from: {0}", - aasSi.Endpoint.ToString()); - } + runtimeOptions?.Log?.Error("Error retrieving AAS from registry! Aborting."); + return; } } } @@ -774,6 +938,9 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var isAllSM = IsValidUriForRepoAllSubmodel(fullItemLocation); if (isAllAAS || isAllSM) { + // ok + operationFound = true; + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, @@ -849,6 +1016,9 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // start with single AAS? if (IsValidUriForRepoSingleAAS(fullItemLocation)) { + // ok + operationFound = true; + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, @@ -870,6 +1040,9 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // start with Submodel? if (IsValidUriForRepoSingleSubmodel(fullItemLocation)) { + // ok + operationFound = true; + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, @@ -891,6 +1064,9 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // start with CD? if (IsValidUriForRepoSingleCD(fullItemLocation)) { + // ok + operationFound = true; + await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, @@ -912,6 +1088,9 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // start with a query? if (IsValidUriForRepoQuery(fullItemLocation)) { + // ok + operationFound = true; + // try extract query from the location var query = ""; var quri = new Uri(fullItemLocation); @@ -1131,6 +1310,15 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // FINALIZE // + // any operation found? + if (!operationFound) + { + runtimeOptions?.Log?.Error("Did not found any matching operation in location to " + + "execute on Registry or Repository! Location was: {0}", + fullItemLocation); + return; + } + // remove, what is not need if (env.AssetAdministrationShellCount() < 1) env.AssetAdministrationShells = null; @@ -1148,7 +1336,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( Cursor = cursor }); } - + public override async Task SaveLocalCopyAsync( string targetFilename, PackCntRuntimeOptions runtimeOptions = null) @@ -1195,34 +1383,41 @@ public enum BaseTypeEnum { Repository, Registry } // public BaseTypeEnum BaseType = BaseTypeEnum.Repository; public BaseTypeEnum BaseType = BaseTypeEnum.Registry; - public bool GetAllAas = true; + public bool GetAllAas; public bool GetSingleAas; - public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; + // public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; + public string AasId = ""; + // public string AasId = "https://phoenixcontact.com/qr/2900542/1/aas/1B"; + + public bool GetAasByAssetLink = true; + public string AssetId = "https://pk.harting.com/?.20P=ZSN1"; public bool GetAllSubmodel; public bool GetSingleSubmodel; - public string SmId = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ=="; + // public string SmId = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ=="; + public string SmId = ""; public bool GetSingleCD; public string CdId; public bool ExecuteQuery; + public string QueryScript = ""; // public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG\r\n filter=\r\n or(\r\n str_contains(sm.IdShort, \"Technical\"),\r\n str_contains(sm.IdShort, \"Nameplate\")\r\n )\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; - public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG$QL\r\n ( contains(sm.idShort, \"Technical\") and\r\n sme.value ge 100 and\r\n sme.value le 200 )\r\n or\r\n ( contains(sm.idShort, \"Nameplate\") and\r\n contains(sme.idShort,\"ManufacturerName\") and\r\n not(contains(sme.value,\"Phoenix\")))\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; - + // public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG$QL\r\n ( contains(sm.idShort, \"Technical\") and\r\n sme.value ge 100 and\r\n sme.value le 200 )\r\n or\r\n ( contains(sm.idShort, \"Nameplate\") and\r\n contains(sme.idShort,\"ManufacturerName\") and\r\n not(contains(sme.value,\"Phoenix\")))\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; + public bool AutoLoadSubmodels = true; public bool AutoLoadCds = true; public bool AutoLoadThumbnails = true; public bool AutoLoadOnDemand = true; - public bool EncryptIds = false; + public bool EncryptIds = true; public bool StayConnected; /// /// Pagenation. Limit to n resulsts. /// - public int PageLimit = 4; + public int PageLimit = 15; /// /// When fetching, skip first n elements of the results @@ -1239,10 +1434,11 @@ public void SetQueryChoices(int choice) { GetAllAas = (choice == 1); GetSingleAas = (choice == 2); - GetAllSubmodel = (choice == 3); - GetSingleSubmodel = (choice == 4); - GetSingleCD = (choice == 5); - ExecuteQuery = (choice == 6); + GetAasByAssetLink = (choice == 3); + GetAllSubmodel = (choice == 4); + GetSingleSubmodel = (choice == 5); + GetSingleCD = (choice == 6); + ExecuteQuery = (choice == 7); } public string GetBaseTypStr() @@ -1255,12 +1451,24 @@ public string GetFetchOperationStr() { var res = "Unknown"; - if (GetAllAas) res = "GetAllAssetAdministrationShells"; - if (GetSingleAas) res = "GetAssetAdministrationShellById"; - if (GetAllSubmodel) res = "GetAllSubmodels"; - if (GetSingleSubmodel) res = "GetSubmodelById"; - if (GetSingleCD) res = "GetConceptDescriptionById"; - if (ExecuteQuery) res = "ExecuteQuery"; + if (BaseType == BaseTypeEnum.Registry) + { + if (GetAllAas) res = "GetAllAssetAdministrationShellDescriptors"; + if (GetSingleAas) res = "GetAssetAdministrationShellDescriptorById"; + if (GetAasByAssetLink) res = "GetAllAssetAdministrationShellIdsByAssetLink"; + if (GetAllSubmodel) res = "GetAllSubmodelDescriptors"; + if (GetSingleSubmodel) res = "GetSubmodelDescriptorById"; + } + + if (BaseType == BaseTypeEnum.Repository) + { + if (GetAllAas) res = "GetAllAssetAdministrationShells"; + if (GetSingleAas) res = "GetAssetAdministrationShellById"; + if (GetAllSubmodel) res = "GetAllSubmodels"; + if (GetSingleSubmodel) res = "GetSubmodelById"; + if (GetSingleCD) res = "GetConceptDescriptionById"; + if (ExecuteQuery) res = "ExecuteQuery"; + } return res; } @@ -1360,6 +1568,20 @@ public static string BuildLocationFrom( var uri = BuildUriForRegistryAllAAS(baseUri, record.PageLimit + record.PageSkip, cursor); return uri.ToString(); } + + // Single AAS? + if (record.GetSingleAas) + { + var uri = BuildUriForRegistrySingleAAS(baseUri, record.AasId, encryptIds: record.EncryptIds); + return uri.ToString(); + } + + // Single AAS? + if (record.GetAasByAssetLink) + { + var uri = BuildUriForRegistryAasByAssetLink(baseUri, record.AssetId, encryptIds: record.EncryptIds); + return uri.ToString(); + } } // @@ -1519,6 +1741,37 @@ public static async Task PerformConnectExtendedDialogue( row += 2; + // AAS(es) by asset link + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "AAS by AssetId", + isChecked: record.GetAasByAssetLink, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => { + if ((bool)o) + record.SetQueryChoices(3); + else + record.GetAasByAssetLink = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + + helper.AddSmallLabelTo(g, row + 1, 0, content: "AssetId:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.AssetId}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.AssetId = s; }); + + row += 2; + // All Submodels AnyUiUIElement.RegisterControl( helper.Set( @@ -1529,7 +1782,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(3); + record.SetQueryChoices(4); else record.GetAllSubmodel = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1546,7 +1799,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(4); + record.SetQueryChoices(5); else record.GetSingleSubmodel = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1577,7 +1830,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(5); + record.SetQueryChoices(6); else record.GetSingleCD = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1608,7 +1861,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(6); + record.SetQueryChoices(7); else record.ExecuteQuery = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1758,6 +2011,141 @@ public static async Task PerformConnectExtendedDialogue( // ok return true; } + + protected class UploadAssistantRecord + { + // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; + public string BaseAddress = "https://eis-data.aas-voyager.com/"; + // public string BaseAddress = "http://smt-repo.admin-shell-io.com/"; + // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; + + public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository; + // public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Registry; + + + } + + public static async Task PerformUploadAssistant( + AasxMenuActionTicket ticket, + AnyUiContextBase displayContext, + string caption, + AdminShellPackageEnvBase packEnv, + IEnumerable idfs) + { + // access + if (displayContext == null || caption?.HasContent() != true || packEnv == null || idfs == null) + return false; + + // if a target file is given, a headless operation occurs +#if __later + if (ticket != null && ticket["Target"] is string targetFn) + { + var exportFmt = -1; + var targExt = System.IO.Path.GetExtension(targetFn).ToLower(); + if (targExt == ".txt") + exportFmt = 0; + if (targExt == ".xlsx") + exportFmt = 1; + if (exportFmt < 0) + { + MainWindowLogic.LogErrorToTicketStatic(ticket, null, + $"For operation '{caption}', the target format could not be " + + $"determined by filename '{targetFn}'. Aborting."); + return; + } + + try + { + WriteTargetFile(exportFmt, targetFn); + } + catch (Exception ex) + { + MainWindowLogic.LogErrorToTicketStatic(ticket, ex, + $"While performing '{caption}'"); + return; + } + + // ok + Log.Singleton.Info("Performed '{0}' and writing report to '{1}'.", + caption, targetFn); + return; + } +#endif + + // build statistics + var numAas = idfs.Where((idf) => idf is Aas.IAssetAdministrationShell).Count(); + var numSm = idfs.Where((idf) => idf is Aas.ISubmodel).Count(); + var numCD = idfs.Where((idf) => idf is Aas.IConceptDescription).Count(); + + // Screen 1 : Data + var record = new UploadAssistantRecord(); + var uc = new AnyUiDialogueDataModalPanel(caption); + uc.ActivateRenderPanel(record, + disableScrollArea: false, + dialogButtons: AnyUiMessageBoxButton.OK, + // extraButtons: new[] { "A", "B" }, + renderPanel: (uci) => + { + // create panel + var panel = new AnyUiStackPanel(); + var helper = new AnyUiSmallWidgetToolkit(); + + var g = helper.AddSmallGrid(25, 2, new[] { "120:", "*" }, + padding: new AnyUiThickness(0, 5, 0, 5), + margin: new AnyUiThickness(10, 0, 30, 0)); + + panel.Add(g); + + // dynamic rows + int row = 0; + + // Base address + Type + helper.AddSmallLabelTo(g, row, 0, content: "Base address:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + var g2 = helper.AddSmallGridTo(g, row, 1, 1, 2, new[] { "#", "*" }); + + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 0, + items: ConnectExtendedRecord.BaseTypeEnumNames, + selectedIndex: (int)record.BaseType, + margin: new AnyUiThickness(0, 0, 5, 0), + padding: new AnyUiThickness(0, -1, 0, -3)), + minWidth: 200, maxWidth: 200), + (i) => { record.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g2, 0, 1, + text: $"{record.BaseAddress}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.BaseAddress = s; }); + + row++; + + // Statistics + helper.AddSmallLabelTo(g, row, 0, content: "Statistics:"); + + helper.AddSmallLabelTo(g, row, 1, content: "# of AAS: " + numAas); + helper.AddSmallLabelTo(g, row+1, 1, content: "# of Submodel: " + numSm); + helper.AddSmallLabelTo(g, row+2, 1, content: "# of ConceptDescriptions: " + numCD); + + row += 3; + + // give back + return g; + }); + + if (!(await displayContext.StartFlyoverModalAsync(uc))) + return false; + + // ok + return true; + } } } From cd32220917f4e6ce57e05e524e9a06e90513d71b Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 21 Oct 2024 12:09:40 +0200 Subject: [PATCH 18/99] * update (before change) --- .../Extensions/ExtendEnvironment.cs | 65 +++++- .../MainWindowAnyUiDialogs.cs | 6 +- .../PackageContainerHttpRepoSubset.cs | 188 ++++++++++++++++-- 3 files changed, 229 insertions(+), 30 deletions(-) diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index 1c5af5ef3..8e9f967ad 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -983,6 +983,31 @@ public static IEnumerable FindAllSubmodelReferences( yield return lr; } + /// + /// Warning: very inefficient! + /// + public static IEnumerable FindAllReferencedCdsForSubmodel( + this AasCore.Aas3_0.IEnvironment env, + ISubmodel sm) + { + // unique set of references + var refs = new List(); + sm?.RecurseOnSubmodelElements(null, (state, parents, sme) => + { + if (sme.SemanticId != null) + { + var cd = env?.FindConceptDescriptionByReference(sme.SemanticId); + if (cd != null) + refs.Add(cd); + } + + // recurse + return true; + }); + + return refs.Distinct(); + } + /// /// Warning: very inefficient! /// @@ -1022,6 +1047,28 @@ public static IEnumerable FindAllSemanticIdsForAas( return refs; } + /// + /// Warning: very inefficient! + /// + public static IEnumerable FindAllReferencedIdentifiablesForAas( + this AasCore.Aas3_0.IEnvironment environment, + IAssetAdministrationShell aas) + { + // unique set of references + var refs = new List(); + foreach (var smr in aas?.AllSubmodels()) + { + var sm = environment.FindSubmodel(smr); + if (sm == null) + continue; + + refs.Add(sm); + refs.AddRange(environment.FindAllReferencedCdsForSubmodel(sm)); + } + + return refs; + } + public static IEnumerable FindAllSubmodelReferencesForAAS( this AasCore.Aas3_0.IEnvironment environment, IAssetAdministrationShell aas) @@ -1040,37 +1087,35 @@ public static IEnumerable FindAllSubmodelReferencesForAAS( /// /// Warning: very inefficient! /// - public static IEnumerable FindAllIdentifiableReferencesFor( + public static IEnumerable FindAllReferencedIdentifiablesFor( this AasCore.Aas3_0.IEnvironment env, IIdentifiable idf, bool makeDistint = true) { // set of references - var refs = new List(); + var refs = new List(); if (idf is IAssetAdministrationShell aas) { - refs.AddRange(env.FindAllSubmodelReferencesForAAS(aas)); - refs.AddRange(env.FindAllSemanticIdsForAas(aas)); + refs.Add(aas); + refs.AddRange(env.FindAllReferencedIdentifiablesForAas(aas)); } if (idf is ISubmodel sm) { - refs.AddRange(env.FindAllSemanticIdsForSubmodel(sm)); + refs.Add(sm); + refs.AddRange(env.FindAllReferencedCdsForSubmodel(sm)); } if (idf is IConceptDescription cd) { - refs.Add(new LocatedReference() { - Identifiable = cd, - Reference = cd.GetReference() - }); + refs.Add(cd); } // more distinct? if (makeDistint) { - var refs2 = refs.Distinct(new LocatedReferenceComparer()); + var refs2 = refs.Distinct(); return refs2; } else diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index b4d3e7022..979cdbac1 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -551,8 +551,7 @@ await val.PerformDialogue(ticket, DisplayContext, .Where((o) => o is Aas.IIdentifiable) .Cast()) { - idfs.AddRange(env.FindAllIdentifiableReferencesFor(idf, makeDistint: false) - .Select((lr) => lr.Identifiable)); + idfs.AddRange(env.FindAllReferencedIdentifiablesFor(idf, makeDistint: false)); } // suffice? @@ -571,7 +570,8 @@ await PackageContainerHttpRepoSubset.PerformUploadAssistant( ticket, DisplayContext, "Upload current to Registry or Repository", PackageCentral.Main, - idfs2); + idfs2, + PackageCentral.CentralRuntimeOptions); } catch (Exception ex) { diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 62bfa7d84..d03b909e1 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -469,7 +469,7 @@ public static bool HasProperty(dynamic obj, string name) /// /// Type of Identifiable /// Type of entity element - protected async Task DownloadListOfIdentifiables( + protected static async Task DownloadListOfIdentifiables( IEnumerable entities, Func lambdaGetLocation, Action lambdaDownloadDoneOrFail, @@ -2012,7 +2012,7 @@ public static async Task PerformConnectExtendedDialogue( return true; } - protected class UploadAssistantRecord + protected class UploadAssistantJobRecord { // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; public string BaseAddress = "https://eis-data.aas-voyager.com/"; @@ -2022,7 +2022,26 @@ protected class UploadAssistantRecord public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository; // public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Registry; + public bool IncludeSubmodels = true; + public bool IncludeCDs = true; + public bool OverwriteIfExist = true; + } + + protected class UploadAssistantElement + { + public string TypeName = ""; + public string Id = ""; + public string IdShort = ""; + public string VersionServer = ""; + public string VersionLocal = ""; + + public bool Upload = true; + } + + protected class UploadAssistantElementsRecord + { + public List Elements = new List(); } public static async Task PerformUploadAssistant( @@ -2030,7 +2049,8 @@ public static async Task PerformUploadAssistant( AnyUiContextBase displayContext, string caption, AdminShellPackageEnvBase packEnv, - IEnumerable idfs) + IEnumerable idfs, + PackCntRuntimeOptions runtimeOptions = null) { // access if (displayContext == null || caption?.HasContent() != true || packEnv == null || idfs == null) @@ -2078,9 +2098,9 @@ public static async Task PerformUploadAssistant( var numCD = idfs.Where((idf) => idf is Aas.IConceptDescription).Count(); // Screen 1 : Data - var record = new UploadAssistantRecord(); + var recordJob = new UploadAssistantJobRecord(); var uc = new AnyUiDialogueDataModalPanel(caption); - uc.ActivateRenderPanel(record, + uc.ActivateRenderPanel(recordJob, disableScrollArea: false, dialogButtons: AnyUiMessageBoxButton.OK, // extraButtons: new[] { "A", "B" }, @@ -2090,7 +2110,7 @@ public static async Task PerformUploadAssistant( var panel = new AnyUiStackPanel(); var helper = new AnyUiSmallWidgetToolkit(); - var g = helper.AddSmallGrid(25, 2, new[] { "120:", "*" }, + var g = helper.AddSmallGrid(25, 2, new[] { "200:", "*" }, padding: new AnyUiThickness(0, 5, 0, 5), margin: new AnyUiThickness(10, 0, 30, 0)); @@ -2099,6 +2119,22 @@ public static async Task PerformUploadAssistant( // dynamic rows int row = 0; + // Statistics + helper.AddSmallLabelTo(g, row, 0, content: "Available for upload:"); + + helper.AddSmallLabelTo(g, row, 1, content: "# of AAS: " + numAas); + helper.AddSmallLabelTo(g, row + 1, 1, content: "# of Submodel: " + numSm); + helper.AddSmallLabelTo(g, row + 2, 1, content: "# of ConceptDescription: " + numCD); + + row += 3; + + // separation + helper.AddSmallBorderTo(g, row, 0, + borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White, + colSpan: 2, + margin: new AnyUiThickness(0, 0, 0, 20)); + row++; + // Base address + Type helper.AddSmallLabelTo(g, row, 0, content: "Base address:", verticalAlignment: AnyUiVerticalAlignment.Center, @@ -2110,31 +2146,55 @@ public static async Task PerformUploadAssistant( helper.Set( helper.AddSmallComboBoxTo(g2, 0, 0, items: ConnectExtendedRecord.BaseTypeEnumNames, - selectedIndex: (int)record.BaseType, + selectedIndex: (int)recordJob.BaseType, margin: new AnyUiThickness(0, 0, 5, 0), padding: new AnyUiThickness(0, -1, 0, -3)), minWidth: 200, maxWidth: 200), - (i) => { record.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); + (i) => { recordJob.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); AnyUiUIElement.SetStringFromControl( helper.Set( helper.AddSmallTextBoxTo(g2, 0, 1, - text: $"{record.BaseAddress}", + text: $"{recordJob.BaseAddress}", verticalAlignment: AnyUiVerticalAlignment.Center, verticalContentAlignment: AnyUiVerticalAlignment.Center), horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { record.BaseAddress = s; }); + (s) => { recordJob.BaseAddress = s; }); row++; - // Statistics - helper.AddSmallLabelTo(g, row, 0, content: "Statistics:"); - - helper.AddSmallLabelTo(g, row, 1, content: "# of AAS: " + numAas); - helper.AddSmallLabelTo(g, row+1, 1, content: "# of Submodel: " + numSm); - helper.AddSmallLabelTo(g, row+2, 1, content: "# of ConceptDescriptions: " + numCD); + // Include Submodels + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Include Submodels (in upload)", + isChecked: recordJob.IncludeSubmodels, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { recordJob.IncludeSubmodels = b; }); - row += 3; + row++; + + // Include CDs + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Include ConceptDescriptions (in upload)", + isChecked: recordJob.IncludeCDs, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { recordJob.IncludeCDs = b; }); + + row++; + + // Overwrite if exists? + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Upload to overwrite on repository, if exists", + isChecked: recordJob.OverwriteIfExist, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { recordJob.OverwriteIfExist = b; }); + + row++; // give back return g; @@ -2143,6 +2203,100 @@ public static async Task PerformUploadAssistant( if (!(await displayContext.StartFlyoverModalAsync(uc))) return false; + // sort Identifiables to look nice + var sorted = idfs.ToList(); + Func rankLambda = (idf) => + { + if (idf is Aas.IAssetAdministrationShell) + return 1; + if (idf is Aas.ISubmodel) + return 2; + if (idf is Aas.IConceptDescription) + return 3; + return 4; + }; + sorted.Sort((i1, i2) => { + if (rankLambda(i1) < rankLambda(i2)) + return -1; + if (rankLambda(i1) > rankLambda(i2)) + return +1; + return i1?.IdShort?.CompareTo(i2?.IdShort) ?? 0; + }); + + if (sorted.Count < 1) + return false; + + // build list + var rows = sorted + .Where((idf) => ( + (!(idf is Aas.ISubmodel) || recordJob.IncludeSubmodels) + && (!(idf is Aas.IConceptDescription) || recordJob.IncludeCDs) + )) + .Select((idf) => new AnyUiDialogueDataGridRow() { + Tag = idf, + Cells = (new[] { + // Type, IdShort, Id, Ver. Local, Exists?, Ver. Server + "" + idf?.GetSelfDescription()?.ElementAbbreviation, + "" + idf?.IdShort, + "" + idf?.Id, + "" + (idf?.Administration?.ToStringExtended(1) ?? "-"), + "New?", + "-" + }).ToList() + }); + + // ask server + if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + // for the Repo, there is simply no other chance than to ask for existence of + // an Identifiable that to try downloading it .. + + var baseUri = new Uri(recordJob.BaseAddress); + + var numRes = await DownloadListOfIdentifiables( + rows, + lambdaGetLocation: (row) => + { + if (!(row.Tag is Aas.IIdentifiable idf)) + return null; + if (idf is Aas.IAssetAdministrationShell) + return BuildUriForRepoSingleAAS(baseUri, idf?.Id, encryptIds: true); + if (idf is Aas.ISubmodel) + return BuildUriForRepoSingleSubmodel(baseUri, idf?.Id, encryptIds: true); + if (idf is Aas.IConceptDescription) + return BuildUriForRepoSingleCD(baseUri, idf?.Id, encryptIds: true); + return null; + }, + runtimeOptions: runtimeOptions, + allowFakeResponses: false, + lambdaDownloadDoneOrFail: (code, sm, contentFn, si) => + { + // error by HTTP? + if (code != HttpStatusCode.OK) + { + return; + } + + // error by no data available? + if (false) + { + + } + }); + } + + // show list of elements + var uc2 = new AnyUiDialogueDataSelectFromDataGrid( + "Select element(s) to be uploaded ..", + maxWidth: 9999); + + uc2.ColumnDefs = AnyUiListOfGridLength.Parse(new[] { "50:", "1*", "3*", "70:", "70:", "70:" }); + uc2.ColumnHeaders = new[] { "Type", "IdShort", "Id", "V.Local", "Exist?", "V.Server" }; + uc2.Rows = rows.ToList(); + + if (!(await displayContext.StartFlyoverModalAsync(uc2))) + return false; + // ok return true; } From 3a7c2f8e2bec923bac8c8fd004660f0cbd35edeb Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 28 Oct 2024 17:11:46 +0100 Subject: [PATCH 19/99] * update --- src/AasxCsharpLibrary/AdminShellUtil.cs | 19 +- src/AasxIntegrationBase/AasxMenu.cs | 2 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 3 +- src/AasxPackageExplorer/debug.MIHO.script | 12 +- .../options-debug.MIHO.json | 1 + src/AasxPackageLogic/DispEditHelperBasics.cs | 1 + .../DispEditHelperEntities.cs | 38 +- src/AasxPackageLogic/ExplorerMenuFactory.cs | 5 +- src/AasxPackageLogic/Options.cs | 5 + .../AdminShellPackageDynamicFetchEnv.cs | 49 +- .../PackageCentral/PackageContainerBase.cs | 5 + .../PackageContainerHttpRepoSubset.cs | 645 ++++++++++++++++-- .../PackageCentral/PackageHttpDownloadUtil.cs | 177 +++-- .../Flyouts/ProgressBarFlyout.xaml | 4 +- .../Flyouts/ProgressBarFlyout.xaml.cs | 16 + .../Flyouts/SelectFromDataGridFlyout.xaml | 2 +- .../Flyouts/SelectFromDataGridFlyout.xaml.cs | 2 +- src/AnyUi/AnyUiDialogueDataBase.cs | 6 + .../Pages/AnyUiFlyoutProgressBar.razor | 43 ++ src/BlazorExplorer/Pages/Index.razor | 6 + 20 files changed, 862 insertions(+), 179 deletions(-) create mode 100644 src/BlazorExplorer/Pages/AnyUiFlyoutProgressBar.razor diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index ef5f59cd8..dc5435c55 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -870,14 +870,29 @@ public static Type GetTypeOrUnderlyingType(Type type, bool resolveGeneric = fals /// for reflection of type specific data. /// Works for most scalars, dateTime, string. /// - public static void SetFieldLazyValue(FieldInfo f, object obj, object value) + public static void SetFieldLazyValue( + FieldInfo f, object obj, object value, + bool enableEnums = false) { // access if (f == null || obj == null) return; + var tut = GetTypeOrUnderlyingType(f.FieldType); + + // enum? + if (enableEnums && tut?.IsEnum == true && value is string vstr) + { + foreach (var v in Enum.GetValues(tut)) + if (v.ToString().ToLower() == vstr.Trim().ToLower()) + { + f.SetValue(obj, v); + } + return; + } + // 2024-01-04: make function more suitable for - switch (Type.GetTypeCode(GetTypeOrUnderlyingType(f.FieldType))) + switch (Type.GetTypeCode(tut)) { case TypeCode.String: f.SetValue(obj, "" + value); diff --git a/src/AasxIntegrationBase/AasxMenu.cs b/src/AasxIntegrationBase/AasxMenu.cs index f06c14b68..5729db473 100644 --- a/src/AasxIntegrationBase/AasxMenu.cs +++ b/src/AasxIntegrationBase/AasxMenu.cs @@ -201,7 +201,7 @@ public bool PopulateObjectFromArgs(object o) foreach (var ad in this) if (ad.Key?.Name?.Trim().ToLower() == name.ToLower()) { - AdminShellUtil.SetFieldLazyValue(f, o, ad.Value); + AdminShellUtil.SetFieldLazyValue(f, o, ad.Value, enableEnums: true); break; } } diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index ffce095df..6ca925a61 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -356,7 +356,8 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() else return ucic.MessageBoxShow(content, text, title, buttons); }, - AllowFakeResponses = Options.Curr.AllowFakeResponses + AllowFakeResponses = Options.Curr.AllowFakeResponses, + ExtendedConnectionDebug = Options.Curr.ExtendedConnectionDebug }; return ro; } diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index b20845984..aeac2fc2b 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,9 +9,15 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -// Tool("connectextended"); -Select("AAS", "First"); -Tool("apiuploadassistant"); +// Tool("connectextended", +// "BaseAddress", "https://eis-data.aas-voyager.com/", +// "BaseType", "Repository", +// "GetSingleAas", "false", +// "GetAllAas", "true", +// "PageLimit", "2", +// "AutoLoadOnDemand", "false"); +// Select("AAS", "First"); +// Tool("apiuploadassistant"); // Select("Submodel", "First"); // Select("Submodel", "Next"); // Tool("exportsmtasciidoc", "File", "C:\Users\homi0002\Desktop\tmp\new.zip", "ExportHtml", "true", "ExportPdf", "false", "AntoraStyle", "false", "ViewResult", "true"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 2de47bfad..7b38417c3 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -104,6 +104,7 @@ "BackupDir": ".\\backup", "BackupFiles": 10, "MaxParallelOps": 1, + "ExtendedConnectionDebug": false, "AllowFakeResponses": true, "RestServerHost": "localhost", "RestServerPort": "1111", diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index 3d7fedb3e..b9e45c2ed 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -2632,6 +2632,7 @@ public bool ImportEclassCDsForTargets(Aas.IEnvironment env, object startMainData "Import ConceptDescriptions from ECLASS", info: "Preparing ...", symbol: AnyUiMessageBoxImage.Information); uc.Progress = 0.0; + // show this this.context.StartFlyover(uc); diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index e123c029b..8e77d9aaa 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -2304,9 +2304,8 @@ public void DisplayOrEditAasEntitySubmodelOrRef( }); this.AddActionPanel( - stack, "ConceptDescriptions (missing):", + stack, "ConceptDescriptions:", repo: repo, superMenu: superMenu, - firstColumnWidth: FirstColumnWidth.Large, ticketMenu: new AasxMenu() .AddAction("create-eclass", "Create \U0001f844 ECLASS", "Create missing CDs searching from ECLASS.") @@ -2314,8 +2313,11 @@ public void DisplayOrEditAasEntitySubmodelOrRef( "Creates an ConceptDescription from this element and " + "assigns the SubmodelElement to it.") .AddAction("create-smes", "Create \U0001f844 SMEs (all)", - "Create missing CDs from semanticId of used SMEs."), - ticketAction: (buttonNdx, ticket) => + "Create missing CDs from semanticId of used SMEs.") + .AddAction("delete-cd-in-repo", "Delete \u274c in Repo", + "Delete ConceptDescriptions which are referenced by semanticId of SubmodelElements " + + "in a given Repository or Registry."), + ticketActionAsync: async (buttonNdx, ticket) => { if (buttonNdx == 0) { @@ -2373,13 +2375,37 @@ public void DisplayOrEditAasEntitySubmodelOrRef( submodel, isExpanded: true); } + if (buttonNdx == 3) + { + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.Submodels, submodel.GetReference()); + + // collect Ids of SubmodelElements.semanticId + var lrs = env?.FindAllSemanticIdsForSubmodel(submodel); + if (lrs == null) + return new AnyUiLambdaActionNone(); + + var cdIds = lrs.Select((lr) => lr?.Reference?.GetAsExactlyOneKey()?.Value); + + // call function + await PackageContainerHttpRepoSubset.AssistantDeleteCDsInRepo( + ticket, context, + "Delete CDs in Repository/ Registry", + cdIds, + runtimeOptions: packages.CentralRuntimeOptions); + + // ok + return new AnyUiLambdaActionNone(); + } + return new AnyUiLambdaActionNone(); }); this.AddActionPanel( - stack, "Submodel & -elements:", + stack, "Submodel&-elems:", repo: repo, superMenu: superMenu, - firstColumnWidth: FirstColumnWidth.Large, ticketMenu: new AasxMenu() .AddAction("upgrade-qualifiers", "Upgrade qualifiers", "Upgrades particular qualifiers from V2.0 to V3.0 for selected element.") diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index 91e30109d..211ed480f 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -12,6 +12,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxIntegrationBase; using AasxPackageLogic; +using AasxPackageLogic.PackageCentral; using AdminShellNS; using Extensions; @@ -107,7 +108,9 @@ public static AasxMenu CreateMainMenu() .AddWpfBlazor(name: "ConnectRest", header: "Connect via REST …", inputGesture: "F6")) .AddSeparator() .AddMenu(header: "API for Registry and Repository …", childs: (new AasxMenu()) - .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …") + .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …", + args: new AasxMenuListOfArgDefs() + .AddFromReflection(new PackageContainerHttpRepoSubset.ConnectExtendedRecord())) .AddWpfBlazor(name: "ApiUploadAssistant", header: "Upload assistant …") ) .AddSeparator() diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index 93e43e08e..a69665109 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -451,6 +451,11 @@ public class OptionsInformation Cmd = "-allow-fake-responses")] public bool AllowFakeResponses = true; + [OptionDescription(Description = + "When connecting to Registry/ Repository, add more details to the log messages.", + Cmd = "-extended-connection-debug")] + public bool ExtendedConnectionDebug = false; + [OptionDescription(Description = "If set, load and store AASX files via temporary package to " + "avoid corruptions. RECOMMENDED!", Cmd = "-indirect-load-save")] diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index 754d278b9..6ca16de9d 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -290,43 +290,22 @@ protected async Task TrySaveAllTaintedIdentifiablesOf( continue; // serialize to memory stream - var res = false; - using (var ms = new MemoryStream()) + var res2 = await PackageHttpDownloadUtil.HttpPutPostIdentifiable( + idf, + destUri: uri); + if (res2 == null || res2.Item1 != HttpStatusCode.OK) { - var jsonWriterOptions = new System.Text.Json.JsonWriterOptions - { - Indented = false - }; - - using (var wr = new System.Text.Json.Utf8JsonWriter(ms, jsonWriterOptions)) - { - // serialize - Jsonization.Serialize.ToJsonObject(idf).WriteTo(wr); - wr.Flush(); - ms.Flush(); - - // prepare for reading again - ms.Seek(0, SeekOrigin.Begin); - - // write - var res2 = await PackageHttpDownloadUtil.HttpPutFromMemoryStream( - ms, - destUri: uri, - allowFakeResponses: _runtimeOptions?.AllowFakeResponses ?? false); - if (!res2) - { - _runtimeOptions?.Log?.Error("Save of modified Identifiable returned error for id={0} at {1}", - idf.Id, - uri.ToString()); - } - else - res = true; - } + _runtimeOptions?.Log?.Error("Save of modified Identifiable returned error {0} for id={1} at {2}", + "" + ((res2 != null) ? (int)res2.Item1 : -1), + idf.Id, + uri.ToString()); + } + else + { + // clear the tainted flag + if (clearTaintedFlags && tidf?.TaintedData != null) + tidf.TaintedData.Tainted = null; } - - // clear the tainted flag - if (res && clearTaintedFlags && tidf?.TaintedData != null) - tidf.TaintedData.Tainted = null; } return count; diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs index 2c15fdd0f..993284765 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs @@ -70,6 +70,11 @@ public delegate AnyUiMessageBoxResult ShowMessageDelegate( /// Allow simulating a repo by predefined JSON web responses. /// public bool AllowFakeResponses = false; + + /// + /// Log more + /// + public bool ExtendedConnectionDebug = false; } /// diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index d03b909e1..6dd113311 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -34,6 +34,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using Newtonsoft.Json; using System.Dynamic; using Newtonsoft.Json.Linq; +using System.ComponentModel; namespace AasxPackageLogic.PackageCentral { @@ -54,7 +55,7 @@ public class PackageContainerHttpRepoSubsetFetchContext : AdminShellPackageDynam /// /// This container represents a subset of AAS elements retrieved from a HTTP / networked repository. /// - [DisplayName("HttpRepoSubset")] + [AasxIntegrationBase.DisplayName("HttpRepoSubset")] public class PackageContainerHttpRepoSubset : PackageContainerRepoItem { /// @@ -267,10 +268,16 @@ public static Uri CombineUri(Uri baseUri, string relativeUri) if (baseUri == null || relativeUri?.HasContent() != true) return null; - if (Uri.TryCreate(baseUri, relativeUri, out var res)) - return res; + // for problems see: + // https://stackoverflow.com/questions/372865/path-combine-for-urls + //if (Uri.TryCreate(baseUri, relativeUri, out var res)) + // return res; + //return null; - return null; + var bu = baseUri.ToString().TrimEnd('/'); + bu += "/" + relativeUri.TrimStart('/'); + + return new Uri(bu); } // @@ -299,15 +306,25 @@ public static Uri BuildUriForRepoAllAAS(Uri baseUri, int pageLimit = 100, string return uri.Uri; } - public static Uri BuildUriForRepoSingleAAS(Uri baseUri, string id, bool encryptIds = true) + public static Uri BuildUriForRepoSingleAAS( + Uri baseUri, string id, + bool encryptIds = true, + bool usePost = false) { // access if (id?.HasContent() != true) return null; // try combine - var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; - return CombineUri(baseUri, $"shells/{smidenc}"); + if (!usePost) + { + var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"shells/{smidenc}"); + } + else + { + return CombineUri(baseUri, "shells"); + } } public static Uri BuildUriForRepoAasThumbnail(Uri baseUri, string id, bool encryptIds = true) @@ -337,18 +354,31 @@ public static Uri BuildUriForRepoAllSubmodel(Uri baseUri, int pageLimit = 100, s return uri.Uri; } - public static Uri BuildUriForRepoSingleSubmodel(Uri baseUri, string id, bool encryptIds = true) + public static Uri BuildUriForRepoSingleSubmodel( + Uri baseUri, string id, + bool encryptIds = true, + bool usePost = false) { // access if (id?.HasContent() != true) return null; // try combine - var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; - return CombineUri(baseUri, $"submodels/{smidenc}"); + if (!usePost) + { + var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"submodels/{smidenc}"); + } + else + { + return CombineUri(baseUri, "submodels"); + } } - public static Uri BuildUriForRepoSingleSubmodel(Uri baseUri, Aas.IReference submodelRef) + public static Uri BuildUriForRepoSingleSubmodel( + Uri baseUri, Aas.IReference submodelRef, + bool encryptIds = true, + bool usePost = false) { // access if (baseUri == null || submodelRef?.IsValid() != true @@ -356,18 +386,29 @@ public static Uri BuildUriForRepoSingleSubmodel(Uri baseUri, Aas.IReference subm return null; // pass on - return BuildUriForRepoSingleSubmodel(baseUri, submodelRef.Keys[0].Value); + return BuildUriForRepoSingleSubmodel(baseUri, submodelRef.Keys[0].Value, + encryptIds: encryptIds, usePost: usePost); } - public static Uri BuildUriForRepoSingleCD(Uri baseUri, string id, bool encryptIds = true) + public static Uri BuildUriForRepoSingleCD( + Uri baseUri, string id, + bool encryptIds = true, + bool usePost = false) { // access if (id?.HasContent() != true) return null; // try combine - var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; - return CombineUri(baseUri, $"concept-descriptions/{smidenc}"); + if (!usePost) + { + var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; + return CombineUri(baseUri, $"concept-descriptions/{smidenc}"); + } + else + { + return CombineUri(baseUri, "concept-descriptions"); + } } /// @@ -471,11 +512,12 @@ public static bool HasProperty(dynamic obj, string name) /// Type of entity element protected static async Task DownloadListOfIdentifiables( IEnumerable entities, - Func lambdaGetLocation, + Func lambdaGetLocation, Action lambdaDownloadDoneOrFail, PackCntRuntimeOptions runtimeOptions = null, bool allowFakeResponses = false, - bool useParallel = false) where T : Aas.IIdentifiable + bool useParallel = false, + Func lambdaGetTypeToSerialize = null) where T : Aas.IIdentifiable { // access if (entities == null) @@ -485,8 +527,19 @@ protected static async Task DownloadListOfIdentifiables( int numRes = 0; // lambda for deserialize - Func lambdaDeserialize = (node) => + Func lambdaDeserialize = (node, ent) => { + if (typeof(T).IsInterface && lambdaGetTypeToSerialize != null) + { + var t = lambdaGetTypeToSerialize(ent); + if (t.IsAssignableTo(typeof(Aas.IAssetAdministrationShell))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.AssetAdministrationShellFrom(node)); + if (t.IsAssignableTo(typeof(Aas.ISubmodel))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.SubmodelFrom(node)); + if (t.IsAssignableTo(typeof(Aas.IConceptDescription))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.ConceptDescriptionFrom(node)); + } + if (typeof(T).IsAssignableFrom(typeof(Aas.IAssetAdministrationShell))) return (T)((Aas.IIdentifiable)Jsonization.Deserialize.AssetAdministrationShellFrom(node)); if (typeof(T).IsAssignableFrom(typeof(Aas.ISubmodel))) @@ -518,7 +571,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - T idf = lambdaDeserialize(node); + T idf = lambdaDeserialize(node, ent); lambdaDownloadDoneOrFail?.Invoke(code, idf, contentFn, ent); if (code == HttpStatusCode.OK) numRes++; @@ -526,6 +579,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( catch (Exception ex) { runtimeOptions?.Log?.Error(ex, $"Parsing downloaded {typeof(T).GetDisplayName()}"); + lambdaDownloadDoneOrFail?.Invoke(HttpStatusCode.UnprocessableEntity, default(T), null, ent); } }); } @@ -554,7 +608,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - T idf = lambdaDeserialize(node); + T idf = lambdaDeserialize(node, ent); lambdaDownloadDoneOrFail?.Invoke(code, idf, contentFn, thisEnt); if (code == HttpStatusCode.OK) numRes++; @@ -562,6 +616,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( catch (Exception ex) { runtimeOptions?.Log?.Error(ex, $"Parsing downloaded {typeof(T).GetDisplayName()}"); + lambdaDownloadDoneOrFail?.Invoke(HttpStatusCode.UnprocessableEntity, default(T), null, thisEnt); } }); }); @@ -1375,59 +1430,102 @@ public class ConnectExtendedRecord public enum BaseTypeEnum { Repository, Registry } public static string[] BaseTypeEnumNames = new[] { "Repository", "Registry" }; + [AasxMenuArgument(help: "Specifies the part of the URI of the Repository/ Registry, which is " + + "common to all operations.")] + public string BaseAddress = ""; // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; // public string BaseAddress = "https://eis-data.aas-voyager.com/"; - // public string BaseAddress = "http://smt-repo.admin-shell-io.com/"; - public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; + // public string BaseAddress = "http://smt-repo.admin-shell-io.com/api/v3.0"; + // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; // public BaseTypeEnum BaseType = BaseTypeEnum.Repository; + [AasxMenuArgument(help: "Either: Repository or Registry")] public BaseTypeEnum BaseType = BaseTypeEnum.Registry; + [AasxMenuArgument(help: "Retrieve all AAS from Repository or Registry. " + + "Note: Use of PageLimit is recommended.")] public bool GetAllAas; - public bool GetSingleAas; + [AasxMenuArgument(help: "Get a single AAS, which is specified by AasId.")] + public bool GetSingleAas = true; + + [AasxMenuArgument(help: "Specicies the Id of the AAS to be retrieved.")] // public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; public string AasId = ""; // public string AasId = "https://phoenixcontact.com/qr/2900542/1/aas/1B"; - public bool GetAasByAssetLink = true; - public string AssetId = "https://pk.harting.com/?.20P=ZSN1"; + [AasxMenuArgument(help: "Get a single AAS, which is specified by a asset link/ asset id.")] + public bool GetAasByAssetLink; + public string AssetId = ""; + // public string AssetId = "https://pk.harting.com/?.20P=ZSN1"; + [AasxMenuArgument(help: "Retrieve all Submodels from Repository or Registry. " + + "Note: Use of PageLimit is recommended.")] public bool GetAllSubmodel; + [AasxMenuArgument(help: "Get a single AAS, which is specified by SmId.")] public bool GetSingleSubmodel; + + [AasxMenuArgument(help: "Specicies the Id of the Submodel to be retrieved.")] // public string SmId = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ=="; public string SmId = ""; + [AasxMenuArgument(help: "Get a single ConceptDescription, which is specified by CdId.")] public bool GetSingleCD; + + [AasxMenuArgument(help: "Specicies the Id of the ConceptDescription to be retrieved.")] public string CdId; + [AasxMenuArgument(help: "Executes a GraphQL query on the Repository/ Registry. ")] public bool ExecuteQuery; + + [AasxMenuArgument(help: "Specicies the contents of the query script to be executed. " + + "Note: Complex syntax and quoting needs to be applied!")] public string QueryScript = ""; // public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG\r\n filter=\r\n or(\r\n str_contains(sm.IdShort, \"Technical\"),\r\n str_contains(sm.IdShort, \"Nameplate\")\r\n )\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; // public string QueryScript = "{\r\n searchSMs(\r\n expression: \"\"\"$LOG$QL\r\n ( contains(sm.idShort, \"Technical\") and\r\n sme.value ge 100 and\r\n sme.value le 200 )\r\n or\r\n ( contains(sm.idShort, \"Nameplate\") and\r\n contains(sme.idShort,\"ManufacturerName\") and\r\n not(contains(sme.value,\"Phoenix\")))\r\n \"\"\"\r\n )\r\n {\r\n url\r\n smId\r\n }\r\n}"; + [AasxMenuArgument(help: "When a AAS is retrieved, try to retrieve Submodels as well. " + + "Note: For this retrieveal, AutoLoadOnDemand may apply.")] public bool AutoLoadSubmodels = true; + + [AasxMenuArgument(help: "When a Submodel is retrieved, try to retrieve ConceptDescriptions " + + "identified by semanticIds as well. " + + "Note: For this retrieveal, AutoLoadOnDemand may apply. " + + "Note: This might significantly increase the number of retrievals.")] public bool AutoLoadCds = true; + + [AasxMenuArgument(help: "When a AAS is retrieved, try to retrieve the associated thumbnail as well.")] public bool AutoLoadThumbnails = true; + + [AasxMenuArgument(help: "When a Submodel/ ConceptDescription is auto-loaded, either load the element " + + "directly (true) or just create a side-information for later fetch.")] public bool AutoLoadOnDemand = true; + + [AasxMenuArgument(help: "Encrypt given Ids.")] public bool EncryptIds = true; + + [AasxMenuArgument(help: "Stay connected with Repository/ Registry and eventually subscribe to " + + "AAS events.")] public bool StayConnected; /// - /// Pagenation. Limit to n resulsts. + /// Pagenation. Limit to n results. /// + [AasxMenuArgument(help: "Pagenation. Limit to n results.")] public int PageLimit = 15; /// - /// When fetching, skip first n elements of the results + /// When fetching, skip first n elements of the results. /// + [AasxMenuArgument(help: "When fetching, skip first n elements of the results.")] public int PageSkip = 0; /// /// This offset in elements is computed by this client by "counting". It does NOT come form /// the server! /// + [JsonIgnore] public int PageOffset; public void SetQueryChoices(int choice) @@ -1641,6 +1739,9 @@ public static async Task PerformConnectExtendedDialogue( // arguments by reflection ticket?.ArgValue?.PopulateObjectFromArgs(record); + if (ticket?.ScriptMode == true) + return true; + // reserve some states for the inner viewing routine bool wrap = false; @@ -2016,41 +2117,26 @@ protected class UploadAssistantJobRecord { // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; public string BaseAddress = "https://eis-data.aas-voyager.com/"; - // public string BaseAddress = "http://smt-repo.admin-shell-io.com/"; + // public string BaseAddress = "http://smt-repo.admin-shell-io.com/api/v3.0"; // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository; // public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Registry; - public bool IncludeSubmodels = true; - public bool IncludeCDs = true; + public bool IncludeSubmodels = false; + public bool IncludeCDs = false; public bool OverwriteIfExist = true; } - protected class UploadAssistantElement - { - public string TypeName = ""; - public string Id = ""; - public string IdShort = ""; - public string VersionServer = ""; - public string VersionLocal = ""; - - public bool Upload = true; - } - - protected class UploadAssistantElementsRecord - { - public List Elements = new List(); - } - public static async Task PerformUploadAssistant( AasxMenuActionTicket ticket, AnyUiContextBase displayContext, string caption, AdminShellPackageEnvBase packEnv, IEnumerable idfs, - PackCntRuntimeOptions runtimeOptions = null) + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null) { // access if (displayContext == null || caption?.HasContent() != true || packEnv == null || idfs == null) @@ -2097,10 +2183,13 @@ public static async Task PerformUploadAssistant( var numSm = idfs.Where((idf) => idf is Aas.ISubmodel).Count(); var numCD = idfs.Where((idf) => idf is Aas.IConceptDescription).Count(); - // Screen 1 : Data + // + // Screen 1 : Job attributes + // + var recordJob = new UploadAssistantJobRecord(); - var uc = new AnyUiDialogueDataModalPanel(caption); - uc.ActivateRenderPanel(recordJob, + var ucJob = new AnyUiDialogueDataModalPanel(caption); + ucJob.ActivateRenderPanel(recordJob, disableScrollArea: false, dialogButtons: AnyUiMessageBoxButton.OK, // extraButtons: new[] { "A", "B" }, @@ -2200,7 +2289,7 @@ public static async Task PerformUploadAssistant( return g; }); - if (!(await displayContext.StartFlyoverModalAsync(uc))) + if (!(await displayContext.StartFlyoverModalAsync(ucJob))) return false; // sort Identifiables to look nice @@ -2235,28 +2324,44 @@ public static async Task PerformUploadAssistant( .Select((idf) => new AnyUiDialogueDataGridRow() { Tag = idf, Cells = (new[] { - // Type, IdShort, Id, Ver. Local, Exists?, Ver. Server + // Type, IdShort, Id, Ver. Local, Method, Ver. Server "" + idf?.GetSelfDescription()?.ElementAbbreviation, "" + idf?.IdShort, "" + idf?.Id, "" + (idf?.Administration?.ToStringExtended(1) ?? "-"), - "New?", + "POST?", "-" }).ToList() - }); - - // ask server - if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + }) + .ToList(); + + // + // Screen 2: make a progress on the check of existence + // + var ucProgExist = new AnyUiDialogueDataProgress( + "Check existence of Identifiables", + info: "Preparing ...", symbol: AnyUiMessageBoxImage.Information); + ucProgExist.Progress = 0.0; + + var numTotal = rows.Count; + var numOK = 0; + var numNOK = 0; + + // setup worker + var workerCheck = new BackgroundWorker(); + workerCheck.DoWork += async (sender, e) => { - // for the Repo, there is simply no other chance than to ask for existence of - // an Identifiable that to try downloading it .. - - var baseUri = new Uri(recordJob.BaseAddress); + // ask server + if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + var baseUri = new Uri(recordJob.BaseAddress); - var numRes = await DownloadListOfIdentifiables( + var numRes = await DownloadListOfIdentifiables( rows, lambdaGetLocation: (row) => { + // return new Uri("https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NSMjU1MTYzUjExNjUvYWFz"); + if (!(row.Tag is Aas.IIdentifiable idf)) return null; if (idf is Aas.IAssetAdministrationShell) @@ -2267,39 +2372,433 @@ public static async Task PerformUploadAssistant( return BuildUriForRepoSingleCD(baseUri, idf?.Id, encryptIds: true); return null; }, + lambdaGetTypeToSerialize: (row) => row.Tag?.GetType(), runtimeOptions: runtimeOptions, allowFakeResponses: false, - lambdaDownloadDoneOrFail: (code, sm, contentFn, si) => + useParallel: Options.Curr.MaxParallelOps > 1, + lambdaDownloadDoneOrFail: (code, idf, contentFn, row) => { + // can change row? + if (row?.Cells == null || row.Cells.Count < 6) + return; + + Action lambdaStat = (found) => + { + if (found) { numOK++; } else { numNOK++; }; + ucProgExist.Info = $"{numOK} entities found, {numNOK} entities missed of {numTotal}\n" + + $"Id: {idf?.Id}"; + ucProgExist.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); + }; + // error by HTTP? - if (code != HttpStatusCode.OK) + if (code == HttpStatusCode.NotFound) { + row.Cells[4] = "POST(404)"; + lambdaStat(false); return; } - // error by no data available? - if (false) + if (code != HttpStatusCode.OK || idf == null) { + row.Cells[4] = $"POST({(int)code})"; + lambdaStat(false); + return; + } + if (row?.Cells != null && row.Cells.Count >= 6) + { + // status + row.Cells[4] = "-"; + if (recordJob.OverwriteIfExist) + row.Cells[4] = "PUT"; + row.Cells[5] = (idf.Administration?.ToStringExtended(1)) ?? "-"; + lambdaStat(true); } }); - } - // show list of elements - var uc2 = new AnyUiDialogueDataSelectFromDataGrid( + } + + ucProgExist.DialogShallClose = true; + + }; + workerCheck.RunWorkerAsync(); + + // close again + await displayContext.StartFlyoverModalAsync(ucProgExist); + + // + // Screen 3: show list of elements + // + var ucSelect = new AnyUiDialogueDataSelectFromDataGrid( "Select element(s) to be uploaded ..", maxWidth: 9999); - uc2.ColumnDefs = AnyUiListOfGridLength.Parse(new[] { "50:", "1*", "3*", "70:", "70:", "70:" }); - uc2.ColumnHeaders = new[] { "Type", "IdShort", "Id", "V.Local", "Exist?", "V.Server" }; - uc2.Rows = rows.ToList(); + ucSelect.ColumnDefs = AnyUiListOfGridLength.Parse(new[] { "50:", "1*", "3*", "70:", "70:", "70:" }); + ucSelect.ColumnHeaders = new[] { "Type", "IdShort", "Id", "V.Local", "Exist?", "V.Server" }; + ucSelect.Rows = rows.ToList(); + ucSelect.EmptySelectOk = true; - if (!(await displayContext.StartFlyoverModalAsync(uc2))) + if (!(await displayContext.StartFlyoverModalAsync(ucSelect))) return false; + // translate result items + var rowsToUpload = ucSelect.ResultItems; + if (rowsToUpload == null || rowsToUpload.Count() < 1) + // nothings means: everything! + rowsToUpload = rows; + + // + // Screen 4: make a progress on the upload of Identifiables + // + var ucProgUpload = new AnyUiDialogueDataProgress( + "Upload of Identifiables", + info: "Preparing ...", symbol: AnyUiMessageBoxImage.Information); + ucProgUpload.Progress = 0.0; + + numTotal = rowsToUpload + .Where((row) => row?.Cells != null && row?.Cells.Count >= 6 && + row.Cells[4].StartsWith("P")) + .Count(); + numOK = 0; + numNOK = 0; + + // setup worker + var workerUpload = new BackgroundWorker(); + workerUpload.DoWork += async (sender, e) => + { + // ask server + if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + var baseUri = new Uri(recordJob.BaseAddress); + + // make a sequential upload, first + foreach (var row in rowsToUpload) + { + // idf? + if (!(row?.Tag is Aas.IIdentifiable idf)) + continue; + + // put / post + var usePost = row.Cells != null && row.Cells.Count >= 6 && + row.Cells[4].StartsWith("POST"); + + // location + Uri location = null; + if (idf is Aas.IAssetAdministrationShell) + location = BuildUriForRepoSingleAAS(baseUri, idf?.Id, encryptIds: true, usePost: usePost); + if (idf is Aas.ISubmodel) + location = BuildUriForRepoSingleSubmodel(baseUri, idf?.Id, encryptIds: true, usePost: usePost); + if (idf is Aas.IConceptDescription) + location = BuildUriForRepoSingleCD(baseUri, idf?.Id, encryptIds: true, usePost: usePost); + if (location == null) + continue; + + var res2 = await PackageHttpDownloadUtil.HttpPutPostIdentifiable( + idf, + destUri: location, + usePost: usePost, + runtimeOptions: runtimeOptions, + containerList: containerList); + + Action lambdaStat = (ok) => + { + if (ok) { numOK++; } else { numNOK++; }; + ucProgUpload.Info = $"{numOK} entities OK, {numNOK} entities NOT OK of {numTotal}\n" + + $"Id: {idf?.Id}"; + ucProgUpload.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); + }; + + if (res2 != null + && (res2.Item1 == HttpStatusCode.Created || res2.Item1 == HttpStatusCode.NoContent)) + { + lambdaStat(true); + } + else + { + runtimeOptions?.Log?.Error("Put/Post of modified Identifiable returned error {0} for id={1} at {2}", + "" + ((res2 != null) ? (int)res2.Item1 : -1), + idf.Id, + location.ToString()); + lambdaStat(false); + } + } + } + + ucProgUpload.DialogShallClose = true; + + }; + workerUpload.RunWorkerAsync(); + + // close again + await displayContext.StartFlyoverModalAsync(ucProgUpload); + + // make stat on Log + if (numNOK > 0) + { + runtimeOptions?.Log?.Error("When Put/ Push to Registry/ Repository, {0} element(s) were not uploaded " + + "while {1} uploaded ok. Location: {2}", + numNOK, numOK, recordJob.BaseAddress); + } + else + { + runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, "Successful Put/ Push of {0} element(s) " + + "to Registry/ Repository. Location {1}", + numOK, recordJob.BaseAddress); + } + // ok return true; } + + protected class DeleteAssistantJobRecord + { + // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; + public string BaseAddress = "https://eis-data.aas-voyager.com/"; + // public string BaseAddress = "http://smt-repo.admin-shell-io.com/api/v3.0"; + // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; + + public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository; + // public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Registry; + } + + public static async Task AssistantDeleteCDsInRepo( + AasxMenuActionTicket ticket, + AnyUiContextBase displayContext, + string caption, + IEnumerable cdIds, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null) + { + // access + if (displayContext == null || caption?.HasContent() != true || cdIds == null || cdIds.Count() < 1) + return false; + + var cdIdsDis = cdIds.Distinct().ToList(); + + // + // Screen 1 : ask for job / Repo + // + + var recordJob = new DeleteAssistantJobRecord(); + var ucJob = new AnyUiDialogueDataModalPanel(caption); + ucJob.ActivateRenderPanel(recordJob, + disableScrollArea: false, + dialogButtons: AnyUiMessageBoxButton.OK, + renderPanel: (uci) => + { + // create panel + var panel = new AnyUiStackPanel(); + var helper = new AnyUiSmallWidgetToolkit(); + + var g = helper.AddSmallGrid(25, 2, new[] { "200:", "*" }, + padding: new AnyUiThickness(0, 5, 0, 5), + margin: new AnyUiThickness(10, 0, 30, 0)); + + panel.Add(g); + + // dynamic rows + int row = 0; + + // Statistics + helper.AddSmallLabelTo(g, row, 0, content: "Requested to delete:"); + helper.AddSmallLabelTo(g, row + 0, 1, content: "# of ConceptDescription: " + cdIds.Count()); + + row += 1; + + // separation + helper.AddSmallBorderTo(g, row, 0, + borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White, + colSpan: 2, + margin: new AnyUiThickness(0, 0, 0, 20)); + row++; + + // Base address + Type + helper.AddSmallLabelTo(g, row, 0, content: "Base address:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + var g2 = helper.AddSmallGridTo(g, row, 1, 1, 2, new[] { "#", "*" }); + + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 0, + items: ConnectExtendedRecord.BaseTypeEnumNames, + selectedIndex: (int)recordJob.BaseType, + margin: new AnyUiThickness(0, 0, 5, 0), + padding: new AnyUiThickness(0, -1, 0, -3)), + minWidth: 200, maxWidth: 200), + (i) => { recordJob.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g2, 0, 1, + text: $"{recordJob.BaseAddress}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { recordJob.BaseAddress = s; }); + + row++; + + // give back + return g; + }); + + if (!(await displayContext.StartFlyoverModalAsync(ucJob))) + return false; + + // + // Screen 2: make a progress on checking, which CDs are existing + // + + var ucProgTest = new AnyUiDialogueDataProgress( + "Checking individual ConceptDescription to exist", + info: "Preparing ...", symbol: AnyUiMessageBoxImage.Information); + ucProgTest.Progress = 0.0; + + var numTotal = cdIdsDis.Count; + var cdExist = new List(); + var numOK = 0; + var numNOK = 0; + + // setup worker + var workerTest = new BackgroundWorker(); + workerTest.DoWork += async (sender, e) => + { + // ask server + if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + var baseUri = new Uri(recordJob.BaseAddress); + + // first, test which ids exist as CD in repo + await DownloadListOfIdentifiables( + cdIdsDis, + lambdaGetLocation: (id) => + { + // return new Uri("https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NSMjU1MTYzUjExNjUvYWFz"); + return BuildUriForRepoSingleCD(baseUri, id, encryptIds: true); + }, + runtimeOptions: runtimeOptions, + useParallel: Options.Curr.MaxParallelOps > 1, + lambdaDownloadDoneOrFail: (code, idf, contentFn, id) => + { + // stat + Action lambdaStat = (ok) => + { + if (ok) { numOK++; } else { numNOK++; }; + ucProgTest.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + + $"Id: {idf?.Id}"; + ucProgTest.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); + }; + + // any error? + if (code != HttpStatusCode.OK || idf == null) + { + lambdaStat(false); + return; + } + + // remember for later + cdExist.Add(idf); + lambdaStat(true); + }); + } + + ucProgTest.DialogShallClose = true; + + }; + workerTest.RunWorkerAsync(); + + // show and close again + await displayContext.StartFlyoverModalAsync(ucProgTest); + + // test + if (cdExist.Count < 1) + { + runtimeOptions?.Log.Error("No ConceptDescriptions to delete found. Aborting! Location: {0}", + recordJob.BaseAddress); + return false; + } + + // ask to proceed? + if (AnyUiMessageBoxResult.Yes != displayContext.MessageBoxFlyoutShow( + $"After checking individual ids, {cdExist.Count} ConceptDescriptions seem to " + + $"exist on the server. Proceed with deleting these?", + "Delete ConceptDescriptions", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + { + runtimeOptions?.Log.Info("Aborted."); + return false; + } + + // + // Screen 4: make a progress on deleting + // + + var ucProgDel = new AnyUiDialogueDataProgress( + "Deleting individual ConceptDescription to exist", + info: "Preparing ...", symbol: AnyUiMessageBoxImage.Information); + ucProgTest.Progress = 0.0; + + numTotal = cdExist.Count; + numOK = 0; + numNOK = 0; + + // setup worker + var workerDel = new BackgroundWorker(); + workerDel.DoWork += async (sender, e) => + { + // ask server + if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + var baseUri = new Uri(recordJob.BaseAddress); + + // first, test which ids exist as CD in repo + + var numRes = await DownloadListOfIdentifiables( + cdIdsDis, + lambdaGetLocation: (id) => + { + // return new Uri("https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NSMjU1MTYzUjExNjUvYWFz"); + return BuildUriForRepoSingleCD(baseUri, id, encryptIds: true); + }, + runtimeOptions: runtimeOptions, + useParallel: Options.Curr.MaxParallelOps > 1, + lambdaDownloadDoneOrFail: (code, idf, contentFn, id) => + { + // stat + Action lambdaStat = (ok) => + { + if (ok) { numOK++; } else { numNOK++; }; + ucProgTest.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + + $"Id: {idf?.Id}"; + ucProgTest.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); + }; + + // any error? + if (code != HttpStatusCode.OK || idf == null) + { + lambdaStat(false); + return; + } + + // remember for later + cdExist.Add(idf); + lambdaStat(true); + }); + } + + ucProgTest.DialogShallClose = true; + + }; + workerDel.RunWorkerAsync(); + + // show and close again + await displayContext.StartFlyoverModalAsync(ucProgDel); + + // okay? + return true; + } } } diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index 8bfc0bda2..0cd708035 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -29,6 +29,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Net.Http.Headers; using System.Text; using AasCore.Aas3_0; +using System.ServiceModel; namespace AasxPackageLogic.PackageCentral { @@ -117,7 +118,9 @@ public static async Task HttpGetToMemoryStreamOLD( var client = new HttpClient(handler); - client.DefaultRequestHeaders.Add("Accept", "application/aas"); + // TODO/CHECK: Basyx does not like this: + // client.DefaultRequestHeaders.Add("Accept", "application/aas"); + client.DefaultRequestHeaders.Add("Accept", "application/json"); client.BaseAddress = new Uri(sourceUri.GetLeftPart(UriPartial.Authority)); var requestPath = sourceUri.PathAndQuery; @@ -329,20 +332,24 @@ public static async Task HttpGetToMemoryStream( var client = new HttpClient(handler); - client.DefaultRequestHeaders.Add("Accept", "application/aas"); + // TODO/CHECK: Basyx does not like this: + // client.DefaultRequestHeaders.Add("Accept", "application/aas"); + client.DefaultRequestHeaders.Add("Accept", "application/json"); client.BaseAddress = new Uri(sourceUri.GetLeftPart(UriPartial.Authority)); var requestPath = sourceUri.PathAndQuery; // Log - runtimeOptions?.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + - $"and request {requestPath} .. "); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + + $"and request {requestPath} .. "); // Token existing? var clhttp = containerList as PackageContainerListHttpRestBase; var oidc = clhttp?.OpenIdClient; if (oidc == null) { - runtimeOptions?.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); if (clhttp != null && OpenIDClient.email != "") { clhttp.OpenIdClient = new OpenIdClientInstance(); @@ -356,14 +363,16 @@ public static async Task HttpGetToMemoryStream( { if (oidc.token != "") { - runtimeOptions?.Log?.Info($" using existing bearer token."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing bearer token."); client.SetBearerToken(oidc.token); } else { if (oidc.email != "") { - runtimeOptions?.Log?.Info($" using existing email token."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing email token."); client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); } } @@ -392,11 +401,13 @@ public static async Task HttpGetToMemoryStream( break; } - runtimeOptions?.Log?.Info("Redirect to: " + splitResult[0]); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info("Redirect to: " + splitResult[0]); if (oidc == null) { - runtimeOptions?.Log?.Info("Creating new OpenIdClient.."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info("Creating new OpenIdClient.."); oidc = new OpenIdClientInstance(); clhttp.OpenIdClient = oidc; clhttp.OpenIdClient.email = OpenIDClient.email; @@ -406,7 +417,8 @@ public static async Task HttpGetToMemoryStream( oidc.authServer = splitResult[0]; - runtimeOptions?.Log?.Info($".. authentication at auth server {oidc.authServer} needed"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($".. authentication at auth server {oidc.authServer} needed"); var response2 = await oidc.RequestTokenAsync(null, GenerateUiLambdaSet(runtimeOptions)); @@ -427,8 +439,9 @@ public static async Task HttpGetToMemoryStream( var contentFn = response.Content.Headers.ContentDisposition?.FileName; // log - runtimeOptions?.Log?.Info($".. response with header-content-len {contentLength} " + - $"and file-name {contentFn} .."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($".. response with header-content-len {contentLength} " + + $"and file-name {contentFn} .."); var contentStream = await response?.Content?.ReadAsStreamAsync(); if (contentStream == null) @@ -440,8 +453,9 @@ public static async Task HttpGetToMemoryStream( var givenFn = sourceUri.ToString(); if (contentFn != null) givenFn = contentFn; - runtimeOptions?.Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " + - $"and request {requestPath} .. "); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " + + $"and request {requestPath} .. "); using (var memStream = new MemoryStream()) { @@ -466,7 +480,8 @@ await memStream.WriteAsync(buffer, 0, bytesRead, if (totalBytesRead > lastBytesRead + deltaSize) { - runtimeOptions?.Log?.Info($".. downloading to memory stream"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($".. downloading to memory stream"); runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Ongoing, contentLength, totalBytesRead); lastBytesRead = totalBytesRead; @@ -478,7 +493,8 @@ await memStream.WriteAsync(buffer, 0, bytesRead, totalBytesRead, totalBytesRead); // log - runtimeOptions?.Log?.Info($".. download done with {totalBytesRead} bytes read!"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($".. download done with {totalBytesRead} bytes read!"); // execute lambda memStream.Flush(); @@ -524,15 +540,17 @@ await memStream.WriteAsync(buffer, 0, bytesRead, var requestPath = sourceUri.PathAndQuery; // Log - runtimeOptions?.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + - $"and request {requestPath} .. "); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + + $"and request {requestPath} .. "); // Token existing? var clhttp = containerList as PackageContainerListHttpRestBase; var oidc = clhttp?.OpenIdClient; if (oidc == null) { - runtimeOptions?.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); if (clhttp != null && OpenIDClient.email != "") { clhttp.OpenIdClient = new OpenIdClientInstance(); @@ -546,14 +564,16 @@ await memStream.WriteAsync(buffer, 0, bytesRead, { if (oidc.token != "") { - runtimeOptions?.Log?.Info($" using existing bearer token."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing bearer token."); client.SetBearerToken(oidc.token); } else { if (oidc.email != "") { - runtimeOptions?.Log?.Info($" using existing email token."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing email token."); client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); } } @@ -581,15 +601,18 @@ await memStream.WriteAsync(buffer, 0, bytesRead, if (splitResult.Length < 1) { + if (runtimeOptions?.ExtendedConnectionDebug == true) runtimeOptions?.Log?.Error("TemporaryRedirect, but url split to successful"); break; } - runtimeOptions?.Log?.Info("Redirect to: " + splitResult[0]); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info("Redirect to: " + splitResult[0]); if (oidc == null) { - runtimeOptions?.Log?.Info("Creating new OpenIdClient.."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info("Creating new OpenIdClient.."); oidc = new OpenIdClientInstance(); clhttp.OpenIdClient = oidc; clhttp.OpenIdClient.email = OpenIDClient.email; @@ -599,7 +622,8 @@ await memStream.WriteAsync(buffer, 0, bytesRead, oidc.authServer = splitResult[0]; - runtimeOptions?.Log?.Info($".. authentication at auth server {oidc.authServer} needed"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($".. authentication at auth server {oidc.authServer} needed"); var response2 = await oidc.RequestTokenAsync(null, GenerateUiLambdaSet(runtimeOptions)); @@ -624,8 +648,9 @@ await memStream.WriteAsync(buffer, 0, bytesRead, var contentFn = response.Content.Headers.ContentDisposition?.FileName; // log - runtimeOptions?.Log?.Info($".. response with header-content-len {contentLength} " + - $"and file-name {contentFn} .."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions .Log?.Info($".. response with header-content-len {contentLength} " + + $"and file-name {contentFn} .."); var contentStream = await response?.Content?.ReadAsStreamAsync(); if (contentStream == null) @@ -637,8 +662,9 @@ await memStream.WriteAsync(buffer, 0, bytesRead, var givenFn = sourceUri.ToString(); if (contentFn != null) givenFn = contentFn; - runtimeOptions?.Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " + - $"and request {requestPath} .. "); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions .Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " + + $"and request {requestPath} .. "); using (var memStream = new MemoryStream()) { @@ -663,7 +689,8 @@ await memStream.WriteAsync(buffer, 0, bytesRead, if (totalBytesRead > lastBytesRead + deltaSize) { - runtimeOptions?.Log?.Info($".. downloading to memory stream"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($".. downloading to memory stream"); runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Ongoing, contentLength, totalBytesRead); lastBytesRead = totalBytesRead; @@ -675,7 +702,8 @@ await memStream.WriteAsync(buffer, 0, bytesRead, totalBytesRead, totalBytesRead); // log - runtimeOptions?.Log?.Info($".. download done with {totalBytesRead} bytes read!"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($".. download done with {totalBytesRead} bytes read!"); // execute lambda memStream.Flush(); @@ -700,9 +728,12 @@ await memStream.WriteAsync(buffer, 0, bytesRead, } else { - Log.Singleton.Error($"HttpPostRequestToMemoryStream server gave status code " + - $"{(int) response.StatusCode} {response.StatusCode}!"); - Log.Singleton.Error(" response content: {0}", responseContents); + if (runtimeOptions?.ExtendedConnectionDebug == true) + { + Log.Singleton.Error($"HttpPostRequestToMemoryStream server gave status code " + + $"{(int)response.StatusCode} {response.StatusCode}!"); + Log.Singleton.Error(" response content: {0}", responseContents); + } return response.StatusCode; } } @@ -711,20 +742,16 @@ await memStream.WriteAsync(buffer, 0, bytesRead, return null; } - public static async Task HttpPutFromMemoryStream( + public static async Task> HttpPutPostFromMemoryStream( MemoryStream ms, Uri destUri, + bool usePost = false, PackCntRuntimeOptions runtimeOptions = null, - PackageContainerListBase containerList = null, - bool allowFakeResponses = false) + PackageContainerListBase containerList = null) { // access if (ms == null || destUri == null) - return false; - - // for the time being: fake responses -> always return true - if (allowFakeResponses) - return true; + return null; // read via HttpClient (uses standard proxies) var handler = new HttpClientHandler(); @@ -737,15 +764,17 @@ public static async Task HttpPutFromMemoryStream( var requestPath = destUri.PathAndQuery; // Log - runtimeOptions?.Log?.Info($"HttpClient PUT() with base-address {client.BaseAddress} " + - $"and request {requestPath} .. "); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($"HttpClient PUT/POSTZ() with base-address {client.BaseAddress} " + + $"and request {requestPath} .. "); // Token existing? var clhttp = containerList as PackageContainerListHttpRestBase; var oidc = clhttp?.OpenIdClient; if (oidc == null) { - runtimeOptions?.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); if (clhttp != null && OpenIDClient.email != "") { clhttp.OpenIdClient = new OpenIdClientInstance(); @@ -759,14 +788,16 @@ public static async Task HttpPutFromMemoryStream( { if (oidc.token != "") { - runtimeOptions?.Log?.Info($" using existing bearer token."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing bearer token."); client.SetBearerToken(oidc.token); } else { if (oidc.email != "") { - runtimeOptions?.Log?.Info($" using existing email token."); + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing email token."); client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); } } @@ -805,19 +836,57 @@ public static async Task HttpPutFromMemoryStream( var data = new ProgressableStreamContent(ms.ToArray(), runtimeOptions); // get response? - using (var response = await client.PutAsync(requestPath, data)) + using (var response = (!usePost) + ? await client.PutAsync(requestPath, data) + : await client.PostAsync(requestPath, data)) { + var content = ""; if (response.IsSuccessStatusCode) - await response.Content.ReadAsStringAsync(); + // TODO: give back? + content = await response.Content.ReadAsStringAsync(); + + // ok! + return new Tuple(response.StatusCode, content); + } + } - if (!response.IsSuccessStatusCode) + public static async Task> HttpPutPostIdentifiable( + Aas.IIdentifiable idf, + Uri destUri, + bool usePost = false, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null) + { + // access + if (idf == null || destUri == null) + return null; + + // serialize to memory stream + using (var ms = new MemoryStream()) + { + var jsonWriterOptions = new System.Text.Json.JsonWriterOptions { - runtimeOptions?.Log?.Error("HttpPutFromMemoryStream Server gave: Operation not allowed!"); - throw new PackageContainerException($"Server operation not allowed!"); - } + Indented = false + }; - // ok! - return true; + using (var wr = new System.Text.Json.Utf8JsonWriter(ms, jsonWriterOptions)) + { + // serialize + Jsonization.Serialize.ToJsonObject(idf).WriteTo(wr); + wr.Flush(); + ms.Flush(); + + // prepare for reading again + ms.Seek(0, SeekOrigin.Begin); + + // write + return await PackageHttpDownloadUtil.HttpPutPostFromMemoryStream( + ms, + destUri: destUri, + runtimeOptions: runtimeOptions, + containerList: containerList, + usePost: usePost); + } } } diff --git a/src/AasxWpfControlLibrary/Flyouts/ProgressBarFlyout.xaml b/src/AasxWpfControlLibrary/Flyouts/ProgressBarFlyout.xaml index 7a53d022a..fe343b98f 100644 --- a/src/AasxWpfControlLibrary/Flyouts/ProgressBarFlyout.xaml +++ b/src/AasxWpfControlLibrary/Flyouts/ProgressBarFlyout.xaml @@ -58,7 +58,9 @@ - + diff --git a/src/AasxWpfControlLibrary/Flyouts/ProgressBarFlyout.xaml.cs b/src/AasxWpfControlLibrary/Flyouts/ProgressBarFlyout.xaml.cs index 17441afe6..4667f4969 100644 --- a/src/AasxWpfControlLibrary/Flyouts/ProgressBarFlyout.xaml.cs +++ b/src/AasxWpfControlLibrary/Flyouts/ProgressBarFlyout.xaml.cs @@ -35,6 +35,8 @@ public partial class ProgressBarFlyout : UserControl, IFlyoutControl // TODO (MIHO, 2020-12-21): make DiaData non-Nullable public AnyUiDialogueDataProgress DiaData = new AnyUiDialogueDataProgress(); + private System.Windows.Threading.DispatcherTimer _timer = null; + public ProgressBarFlyout(string caption = null, string info = null, AnyUiMessageBoxImage? symbol = null) { InitializeComponent(); @@ -46,6 +48,20 @@ public ProgressBarFlyout(string caption = null, string info = null, AnyUiMessage DiaData.Info = info; if (symbol.HasValue) DiaData.Symbol = symbol.Value; + + // timer + _timer = new System.Windows.Threading.DispatcherTimer(); + _timer.Interval = new TimeSpan(0, 0, 0, 0, 100); + _timer.Start(); + _timer.Tick += (object sender, EventArgs e) => + { + if (DiaData?.DialogShallClose == true) + { + if (this._timer != null) + this._timer.Stop(); + ControlClosed?.Invoke(); + } + }; } private void UserControl_Loaded(object sender, RoutedEventArgs e) diff --git a/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml b/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml index c3118998d..19ce59053 100644 --- a/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml +++ b/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml @@ -70,7 +70,7 @@ --> - + + +} + +@code { + [Parameter] + public AnyUiHtmlEventSession EventSession { get; set; } + + [Parameter] + public AnyUiDialogueDataBase DialogueData { get; set; } + + public string TextLines = ""; + + public void LeaveResult(bool result) + { + EventSession?.EndModal(result); + } +} diff --git a/src/BlazorExplorer/Pages/Index.razor b/src/BlazorExplorer/Pages/Index.razor index 399cb5549..8a46f43db 100644 --- a/src/BlazorExplorer/Pages/Index.razor +++ b/src/BlazorExplorer/Pages/Index.razor @@ -102,6 +102,12 @@ DialogueData="evs.DialogueData" /> } + @if (evs.DialogueData is AnyUiDialogueDataProgress) + { + + } + @if (evs.DialogueData is AnyUiDialogueDataOpenFile) { Date: Mon, 28 Oct 2024 17:47:32 +0100 Subject: [PATCH 20/99] * update --- .../PackageContainerHttpRepoSubset.cs | 227 ++---------- .../PackageCentral/PackageHttpDownloadUtil.cs | 334 ++++++++++++++++++ 2 files changed, 355 insertions(+), 206 deletions(-) diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 6dd113311..7657b8b7a 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -503,191 +503,7 @@ public static bool HasProperty(dynamic obj, string name) return objType.GetProperty(name) != null; } - /// - /// This utility is able to parallel download Identifiables and will call lambda upon. - /// Insted of a list of location, it is taking a list of objects (entities) and a lambda - /// to extract the location from. - /// - /// Type of Identifiable - /// Type of entity element - protected static async Task DownloadListOfIdentifiables( - IEnumerable entities, - Func lambdaGetLocation, - Action lambdaDownloadDoneOrFail, - PackCntRuntimeOptions runtimeOptions = null, - bool allowFakeResponses = false, - bool useParallel = false, - Func lambdaGetTypeToSerialize = null) where T : Aas.IIdentifiable - { - // access - if (entities == null) - return 0; - - // result - int numRes = 0; - - // lambda for deserialize - Func lambdaDeserialize = (node, ent) => - { - if (typeof(T).IsInterface && lambdaGetTypeToSerialize != null) - { - var t = lambdaGetTypeToSerialize(ent); - if (t.IsAssignableTo(typeof(Aas.IAssetAdministrationShell))) - return (T)((Aas.IIdentifiable)Jsonization.Deserialize.AssetAdministrationShellFrom(node)); - if (t.IsAssignableTo(typeof(Aas.ISubmodel))) - return (T)((Aas.IIdentifiable)Jsonization.Deserialize.SubmodelFrom(node)); - if (t.IsAssignableTo(typeof(Aas.IConceptDescription))) - return (T)((Aas.IIdentifiable)Jsonization.Deserialize.ConceptDescriptionFrom(node)); - } - - if (typeof(T).IsAssignableFrom(typeof(Aas.IAssetAdministrationShell))) - return (T)((Aas.IIdentifiable)Jsonization.Deserialize.AssetAdministrationShellFrom(node)); - if (typeof(T).IsAssignableFrom(typeof(Aas.ISubmodel))) - return (T)((Aas.IIdentifiable)Jsonization.Deserialize.SubmodelFrom(node)); - if (typeof(T).IsAssignableFrom(typeof(Aas.IConceptDescription))) - return (T)((Aas.IIdentifiable)Jsonization.Deserialize.ConceptDescriptionFrom(node)); - return default(T); - }; - - // over all locations - if (!useParallel) - { - foreach (var ent in entities) - { - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: lambdaGetLocation?.Invoke(ent), - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDoneOrFail: (code, ms, contentFn) => - { - // not OK? - if (code != HttpStatusCode.OK) - { - lambdaDownloadDoneOrFail?.Invoke(code, default(T), null, ent); - return; - } - - // go on - try - { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - T idf = lambdaDeserialize(node, ent); - lambdaDownloadDoneOrFail?.Invoke(code, idf, contentFn, ent); - if (code == HttpStatusCode.OK) - numRes++; - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, $"Parsing downloaded {typeof(T).GetDisplayName()}"); - lambdaDownloadDoneOrFail?.Invoke(HttpStatusCode.UnprocessableEntity, default(T), null, ent); - } - }); - } - } - else - { - await Parallel.ForEachAsync(entities, - new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, - async (ent, token) => - { - var thisEnt = ent; - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: lambdaGetLocation?.Invoke(ent), - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDoneOrFail: (code, ms, contentFn) => - { - // not OK? - if (code != HttpStatusCode.OK) - { - lambdaDownloadDoneOrFail?.Invoke(code, default(T), null, ent); - return; - } - - // go on - try - { - var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - T idf = lambdaDeserialize(node, ent); - lambdaDownloadDoneOrFail?.Invoke(code, idf, contentFn, thisEnt); - if (code == HttpStatusCode.OK) - numRes++; - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, $"Parsing downloaded {typeof(T).GetDisplayName()}"); - lambdaDownloadDoneOrFail?.Invoke(HttpStatusCode.UnprocessableEntity, default(T), null, thisEnt); - } - }); - }); - - } - - // ok - return numRes; - } - - protected async Task DownloadIdentifiableToOK( - Uri location, - PackCntRuntimeOptions runtimeOptions = null, - bool allowFakeResponses = false) where T : Aas.IIdentifiable - { - T res = default(T); - - await DownloadListOfIdentifiables( - new[] { location }, - lambdaGetLocation: (loc) => loc, - runtimeOptions: runtimeOptions, - allowFakeResponses: allowFakeResponses, - lambdaDownloadDoneOrFail: (code, idf, contentFn, ent) => - { - if (code == HttpStatusCode.OK) - res = idf; - }); - - return res; - } - - /// - /// Can download arbitrary dynamic entity. - /// - /// Either dynamic object or null - protected async Task DownloadEntityToDynamicObject( - Uri uri, - PackCntRuntimeOptions runtimeOptions = null, - bool allowFakeResponses = false) - { - // prepare receing the descriptors - dynamic resObj = null; - - // GET - await PackageHttpDownloadUtil.HttpGetToMemoryStream( - sourceUri: uri, - allowFakeResponses: allowFakeResponses, - runtimeOptions: runtimeOptions, - lambdaDownloadDoneOrFail: (code, ms, contentFn) => - { - if (code != HttpStatusCode.OK) - return; - - try - { - // try working with dynamic objects - using (StreamReader reader = new StreamReader(ms, System.Text.Encoding.UTF8, true)) - using (var jsonTextReader = new JsonTextReader(reader)) - { - JsonSerializer serializer = new JsonSerializer(); - resObj = serializer.Deserialize(jsonTextReader); - } - } - catch (Exception ex) - { - runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); - } - }); - - return resObj; - } + private async Task FromRegistryGetAasAndSubmodels( OnDemandListIdentifiable prepAas, @@ -747,7 +563,7 @@ private async Task FromRegistryGetAasAndSubmodels( } // ok - var aas = await DownloadIdentifiableToOK( + var aas = await PackageHttpDownloadUtil.DownloadIdentifiableToOK( aasSi.Endpoint, runtimeOptions, allowFakeResponses); if (aas == null) { @@ -791,7 +607,7 @@ private async Task FromRegistryGetAasAndSubmodels( if (!record.AutoLoadOnDemand) { // be prepared to download them - var numRes = await DownloadListOfIdentifiables( + var numRes = await PackageHttpDownloadUtil.DownloadListOfIdentifiables( smRegged, lambdaGetLocation: (si) => si.Endpoint, runtimeOptions: runtimeOptions, @@ -895,7 +711,7 @@ public override async Task LoadFromSourceAsync( operationFound = true; // prepare receing the descriptors - var resObj = await DownloadEntityToDynamicObject( + var resObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( new Uri(fullItemLocation), runtimeOptions, allowFakeResponses); // Note: the result format for GetAllAAS and GetAllAssetAdministrationShellIdsByAssetLink @@ -917,7 +733,7 @@ public override async Task LoadFromSourceAsync( { // in res, have only an id. Get the descriptor var id = "" + res; - var singleDesc = await DownloadEntityToDynamicObject( + var singleDesc = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( BuildUriForRegistrySingleAAS(baseUri, id, encryptIds: true), runtimeOptions, allowFakeResponses); if (singleDesc == null || !HasProperty(singleDesc, "endpoints")) @@ -2356,7 +2172,7 @@ public static async Task PerformUploadAssistant( { var baseUri = new Uri(recordJob.BaseAddress); - var numRes = await DownloadListOfIdentifiables( + var numRes = await PackageHttpDownloadUtil.DownloadListOfIdentifiables( rows, lambdaGetLocation: (row) => { @@ -2671,7 +2487,7 @@ public static async Task AssistantDeleteCDsInRepo( var baseUri = new Uri(recordJob.BaseAddress); // first, test which ids exist as CD in repo - await DownloadListOfIdentifiables( + await PackageHttpDownloadUtil.DownloadListOfIdentifiables( cdIdsDis, lambdaGetLocation: (id) => { @@ -2715,7 +2531,8 @@ public static async Task AssistantDeleteCDsInRepo( // test if (cdExist.Count < 1) { - runtimeOptions?.Log.Error("No ConceptDescriptions to delete found. Aborting! Location: {0}", + runtimeOptions?.Log.Info(StoredPrint.Color.Blue, + "No ConceptDescriptions to delete found. Finalizing! Location: {0}", recordJob.BaseAddress); return false; } @@ -2753,42 +2570,40 @@ public static async Task AssistantDeleteCDsInRepo( { var baseUri = new Uri(recordJob.BaseAddress); - // first, test which ids exist as CD in repo - - var numRes = await DownloadListOfIdentifiables( - cdIdsDis, - lambdaGetLocation: (id) => + // second, send deletes + await PackageHttpDownloadUtil.DeleteListOfEntities( + cdExist, + lambdaGetLocation: (cd) => { // return new Uri("https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NSMjU1MTYzUjExNjUvYWFz"); - return BuildUriForRepoSingleCD(baseUri, id, encryptIds: true); + return BuildUriForRepoSingleCD(baseUri, cd.Id, encryptIds: true); }, runtimeOptions: runtimeOptions, useParallel: Options.Curr.MaxParallelOps > 1, - lambdaDownloadDoneOrFail: (code, idf, contentFn, id) => + lambdaDeleteDoneOrFail: (code, content, cd2) => { // stat Action lambdaStat = (ok) => { if (ok) { numOK++; } else { numNOK++; }; - ucProgTest.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + - $"Id: {idf?.Id}"; - ucProgTest.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); + ucProgDel.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + + $"Id: {cd2?.Id}"; + ucProgDel.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); }; // any error? - if (code != HttpStatusCode.OK || idf == null) + if (code != HttpStatusCode.OK && code != HttpStatusCode.NoContent) { lambdaStat(false); return; } - // remember for later - cdExist.Add(idf); + // ok lambdaStat(true); }); } - ucProgTest.DialogShallClose = true; + ucProgDel.DialogShallClose = true; }; workerDel.RunWorkerAsync(); diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index 0cd708035..1503affc0 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -30,6 +30,8 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Text; using AasCore.Aas3_0; using System.ServiceModel; +using Namotion.Reflection; +using System.Text.Json.Nodes; namespace AasxPackageLogic.PackageCentral { @@ -850,6 +852,103 @@ public static async Task> HttpPutPostFromMemoryStr } } + public static async Task> HttpDeleteUri( + Uri delUri, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null) + { + // access + if (delUri == null) + return null; + + // read via HttpClient (uses standard proxies) + var handler = new HttpClientHandler(); + handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; + + // new http client + var client = new HttpClient(handler); + + client.BaseAddress = new Uri(delUri.GetLeftPart(UriPartial.Authority)); + var requestPath = delUri.PathAndQuery; + + // Log + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($"HttpClient DELETE() with base-address {client.BaseAddress} " + + $"and request {requestPath} .. "); + + // Token existing? + var clhttp = containerList as PackageContainerListHttpRestBase; + var oidc = clhttp?.OpenIdClient; + if (oidc == null) + { + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (clhttp != null && OpenIDClient.email != "") + { + clhttp.OpenIdClient = new OpenIdClientInstance(); + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + oidc = clhttp.OpenIdClient; + } + } + if (oidc != null) + { + if (oidc.token != "") + { + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing bearer token."); + client.SetBearerToken(oidc.token); + } + else + { + if (oidc.email != "") + { + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing email token."); + client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); + } + } + } + + // BEGIN Workaround behind some proxies + // Stream is sent twice, if proxy-authorization header is not set + string proxyFile = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "/proxy.dat"; + string username = ""; + string password = ""; + if (System.IO.File.Exists(proxyFile)) + { + using (StreamReader sr = new StreamReader(proxyFile)) + { + // ReSharper disable MethodHasAsyncOverload + sr.ReadLine(); + username = sr.ReadLine(); + password = sr.ReadLine(); + // ReSharper enable MethodHasAsyncOverload + } + } + if (username != "" && password != "") + { + var authToken = Encoding.ASCII.GetBytes(username + ":" + password); + client.DefaultRequestHeaders.ProxyAuthorization = new AuthenticationHeaderValue("Basic", + Convert.ToBase64String(authToken)); + } + // END Workaround behind some proxies + + // get response? + using (var response = await client.DeleteAsync(requestPath)) + { + // try read in any case, despite: + // https://stackoverflow.com/questions/17640666/httpclient-deleteasync-and-content-readadstringasync-always-return-null + var content = ""; + if (response.StatusCode != HttpStatusCode.NoContent) + await response.Content.ReadAsStringAsync(); + + // ok! + return new Tuple(response.StatusCode, content); + } + } + public static async Task> HttpPutPostIdentifiable( Aas.IIdentifiable idf, Uri destUri, @@ -890,5 +989,240 @@ public static async Task> HttpPutPostIdentifiable( } } + /// + /// This utility is able to parallel download Identifiables and will call lambda upon. + /// Insted of a list of location, it is taking a list of objects (entities) and a lambda + /// to extract the location from. + /// + /// Type of Identifiable + /// Type of entity element + public static async Task DownloadListOfIdentifiables( + IEnumerable entities, + Func lambdaGetLocation, + Action lambdaDownloadDoneOrFail, + PackCntRuntimeOptions runtimeOptions = null, + bool allowFakeResponses = false, + bool useParallel = false, + Func lambdaGetTypeToSerialize = null) where T : Aas.IIdentifiable + { + // access + if (entities == null) + return 0; + + // result + int numRes = 0; + + // lambda for deserialize + Func lambdaDeserialize = (node, ent) => + { + if (typeof(T).IsInterface && lambdaGetTypeToSerialize != null) + { + var t = lambdaGetTypeToSerialize(ent); + if (t.IsAssignableTo(typeof(Aas.IAssetAdministrationShell))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.AssetAdministrationShellFrom(node)); + if (t.IsAssignableTo(typeof(Aas.ISubmodel))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.SubmodelFrom(node)); + if (t.IsAssignableTo(typeof(Aas.IConceptDescription))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.ConceptDescriptionFrom(node)); + } + + if (typeof(T).IsAssignableFrom(typeof(Aas.IAssetAdministrationShell))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.AssetAdministrationShellFrom(node)); + if (typeof(T).IsAssignableFrom(typeof(Aas.ISubmodel))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.SubmodelFrom(node)); + if (typeof(T).IsAssignableFrom(typeof(Aas.IConceptDescription))) + return (T)((Aas.IIdentifiable)Jsonization.Deserialize.ConceptDescriptionFrom(node)); + return default(T); + }; + + // over all locations + if (!useParallel) + { + foreach (var ent in entities) + { + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: lambdaGetLocation?.Invoke(ent), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: (code, ms, contentFn) => + { + // not OK? + if (code != HttpStatusCode.OK) + { + lambdaDownloadDoneOrFail?.Invoke(code, default(T), null, ent); + return; + } + + // go on + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + T idf = lambdaDeserialize(node, ent); + lambdaDownloadDoneOrFail?.Invoke(code, idf, contentFn, ent); + if (code == HttpStatusCode.OK) + numRes++; + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, $"Parsing downloaded {typeof(T).GetDisplayName()}"); + lambdaDownloadDoneOrFail?.Invoke(HttpStatusCode.UnprocessableEntity, default(T), null, ent); + } + }); + } + } + else + { + await Parallel.ForEachAsync(entities, + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (ent, token) => + { + var thisEnt = ent; + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + sourceUri: lambdaGetLocation?.Invoke(ent), + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: (code, ms, contentFn) => + { + // not OK? + if (code != HttpStatusCode.OK) + { + lambdaDownloadDoneOrFail?.Invoke(code, default(T), null, ent); + return; + } + + // go on + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + T idf = lambdaDeserialize(node, ent); + lambdaDownloadDoneOrFail?.Invoke(code, idf, contentFn, thisEnt); + if (code == HttpStatusCode.OK) + numRes++; + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, $"Parsing downloaded {typeof(T).GetDisplayName()}"); + lambdaDownloadDoneOrFail?.Invoke(HttpStatusCode.UnprocessableEntity, default(T), null, thisEnt); + } + }); + }); + + } + + // ok + return numRes; + } + + public static async Task DownloadIdentifiableToOK( + Uri location, + PackCntRuntimeOptions runtimeOptions = null, + bool allowFakeResponses = false) where T : Aas.IIdentifiable + { + T res = default(T); + + await DownloadListOfIdentifiables( + new[] { location }, + lambdaGetLocation: (loc) => loc, + runtimeOptions: runtimeOptions, + allowFakeResponses: allowFakeResponses, + lambdaDownloadDoneOrFail: (code, idf, contentFn, ent) => + { + if (code == HttpStatusCode.OK) + res = idf; + }); + + return res; + } + + /// + /// Can download arbitrary dynamic entity. + /// + /// Either dynamic object or null + public static async Task DownloadEntityToDynamicObject( + Uri uri, + PackCntRuntimeOptions runtimeOptions = null, + bool allowFakeResponses = false) + { + // prepare receing the descriptors + dynamic resObj = null; + + // GET + await HttpGetToMemoryStream( + sourceUri: uri, + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: (code, ms, contentFn) => + { + if (code != HttpStatusCode.OK) + return; + + try + { + // try working with dynamic objects + using (StreamReader reader = new StreamReader(ms, System.Text.Encoding.UTF8, true)) + using (var jsonTextReader = new JsonTextReader(reader)) + { + JsonSerializer serializer = new JsonSerializer(); + resObj = serializer.Deserialize(jsonTextReader); + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing initially downloaded AAS"); + } + }); + + return resObj; + } + + public static async Task DeleteListOfEntities( + IEnumerable entities, + Func lambdaGetLocation, + Action lambdaDeleteDoneOrFail, + PackCntRuntimeOptions runtimeOptions = null, + bool allowFakeResponses = false, + bool useParallel = false) + { + // access + if (entities == null) + return 0; + + // result + int numRes = 0; + + // over all locations + if (!useParallel) + { + foreach (var ent in entities) + { + // delete + var res = await HttpDeleteUri( + delUri: lambdaGetLocation?.Invoke(ent), + runtimeOptions: runtimeOptions); + + // tell + lambdaDeleteDoneOrFail?.Invoke(res.Item1, res.Item2, ent); + } + } + else + { + await Parallel.ForEachAsync(entities, + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (ent, token) => + { + // delete + var res = await HttpDeleteUri( + delUri: lambdaGetLocation?.Invoke(ent), + runtimeOptions: runtimeOptions); + + // tell + lambdaDeleteDoneOrFail?.Invoke(res.Item1, res.Item2, ent); + }); + + } + + // ok + return numRes; + } } } From e5ba065c1aac93f8f57f2828a569cbd913f78ec5 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 28 Oct 2024 22:10:26 +0100 Subject: [PATCH 21/99] * update --- .../AdminShellPackageDynamicFetchEnv.cs | 4 +- .../PackageContainerHttpRepoSubset.cs | 46 ++- .../PackageCentral/PackageHttpDownloadUtil.cs | 295 +++++++----------- 3 files changed, 144 insertions(+), 201 deletions(-) diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index 6ca16de9d..c2461d17e 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -139,6 +139,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( return null; await PackageHttpDownloadUtil.HttpGetToMemoryStream( + null, sourceUri: loc, allowFakeResponses: _runtimeOptions?.AllowFakeResponses ?? false, runtimeOptions: _runtimeOptions, @@ -291,7 +292,8 @@ protected async Task TrySaveAllTaintedIdentifiablesOf( // serialize to memory stream var res2 = await PackageHttpDownloadUtil.HttpPutPostIdentifiable( - idf, + reUseClient: null, + idf: idf, destUri: uri); if (res2 == null || res2.Item1 != HttpStatusCode.OK) { diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 7657b8b7a..d0b5a7578 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -502,9 +502,7 @@ public static bool HasProperty(dynamic obj, string name) return objType.GetProperty(name) != null; } - - - + private async Task FromRegistryGetAasAndSubmodels( OnDemandListIdentifiable prepAas, OnDemandListIdentifiable prepSM, @@ -608,6 +606,7 @@ private async Task FromRegistryGetAasAndSubmodels( { // be prepared to download them var numRes = await PackageHttpDownloadUtil.DownloadListOfIdentifiables( + null, smRegged, lambdaGetLocation: (si) => si.Endpoint, runtimeOptions: runtimeOptions, @@ -756,6 +755,7 @@ await FromRegistryGetAasAndSubmodels( // GET await PackageHttpDownloadUtil.HttpGetToMemoryStream( + null, sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, @@ -2150,7 +2150,16 @@ public static async Task PerformUploadAssistant( }).ToList() }) .ToList(); - + + // in order to re-use sockets + Uri baseUri = null; + HttpClient client = null; + if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + baseUri = new Uri(recordJob.BaseAddress); + client = PackageHttpDownloadUtil.CreateHttpClient(baseUri, runtimeOptions, containerList); + } + // // Screen 2: make a progress on the check of existence // @@ -2170,14 +2179,12 @@ public static async Task PerformUploadAssistant( // ask server if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { - var baseUri = new Uri(recordJob.BaseAddress); - var numRes = await PackageHttpDownloadUtil.DownloadListOfIdentifiables( + client, rows, lambdaGetLocation: (row) => { // return new Uri("https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NSMjU1MTYzUjExNjUvYWFz"); - if (!(row.Tag is Aas.IIdentifiable idf)) return null; if (idf is Aas.IAssetAdministrationShell) @@ -2285,8 +2292,6 @@ public static async Task PerformUploadAssistant( // ask server if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { - var baseUri = new Uri(recordJob.BaseAddress); - // make a sequential upload, first foreach (var row in rowsToUpload) { @@ -2295,9 +2300,14 @@ public static async Task PerformUploadAssistant( continue; // put / post + var usePut = row.Cells != null && row.Cells.Count >= 6 && + row.Cells[4].StartsWith("PUT"); var usePost = row.Cells != null && row.Cells.Count >= 6 && row.Cells[4].StartsWith("POST"); + if (!usePut && !usePost) + continue; + // location Uri location = null; if (idf is Aas.IAssetAdministrationShell) @@ -2310,6 +2320,7 @@ public static async Task PerformUploadAssistant( continue; var res2 = await PackageHttpDownloadUtil.HttpPutPostIdentifiable( + client, idf, destUri: location, usePost: usePost, @@ -2477,6 +2488,15 @@ public static async Task AssistantDeleteCDsInRepo( var numOK = 0; var numNOK = 0; + // in order to re-use sockets + Uri baseUri = null; + HttpClient client = null; + if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + baseUri = new Uri(recordJob.BaseAddress); + client = PackageHttpDownloadUtil.CreateHttpClient(baseUri, runtimeOptions, containerList); + } + // setup worker var workerTest = new BackgroundWorker(); workerTest.DoWork += async (sender, e) => @@ -2484,10 +2504,9 @@ public static async Task AssistantDeleteCDsInRepo( // ask server if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { - var baseUri = new Uri(recordJob.BaseAddress); - // first, test which ids exist as CD in repo await PackageHttpDownloadUtil.DownloadListOfIdentifiables( + client, cdIdsDis, lambdaGetLocation: (id) => { @@ -2572,6 +2591,7 @@ public static async Task AssistantDeleteCDsInRepo( // second, send deletes await PackageHttpDownloadUtil.DeleteListOfEntities( + client, cdExist, lambdaGetLocation: (cd) => { @@ -2612,6 +2632,10 @@ public static async Task AssistantDeleteCDsInRepo( await displayContext.StartFlyoverModalAsync(ucProgDel); // okay? + runtimeOptions?.Log.Info(StoredPrint.Color.Blue, + "{0} ConceptDescriptions deleted successfull, {1} NOK. Location: {2}", + numOK, numNOK, recordJob.BaseAddress); + return true; } } diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index 1503affc0..7dc21f702 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -84,6 +84,86 @@ private static OpenIdClientInstance.UiLambdaSet GenerateUiLambdaSet(PackCntRunti return res; } + public static HttpClient CreateHttpClient( + Uri baseUri, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null) + { + // read via HttpClient (uses standard proxies) + var handler = new HttpClientHandler(); + handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; + handler.AllowAutoRedirect = false; + + // new http client + var client = new HttpClient(handler); + + // TODO/CHECK: Basyx does not like this: + // client.DefaultRequestHeaders.Add("Accept", "application/aas"); + client.DefaultRequestHeaders.Add("Accept", "application/json"); + client.BaseAddress = new Uri(baseUri.GetLeftPart(UriPartial.Authority)); + + // Token existing? + var clhttp = containerList as PackageContainerListHttpRestBase; + var oidc = clhttp?.OpenIdClient; + if (oidc == null) + { + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); + if (clhttp != null && OpenIDClient.email != "") + { + clhttp.OpenIdClient = new OpenIdClientInstance(); + clhttp.OpenIdClient.email = OpenIDClient.email; + clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; + clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; + oidc = clhttp.OpenIdClient; + } + } + if (oidc != null) + { + if (oidc.token != "") + { + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing bearer token."); + client.SetBearerToken(oidc.token); + } + else + { + if (oidc.email != "") + { + if (runtimeOptions?.ExtendedConnectionDebug == true) + runtimeOptions.Log?.Info($" using existing email token."); + client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); + } + } + } + + // BEGIN Workaround behind some proxies + // Stream is sent twice, if proxy-authorization header is not set + string proxyFile = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "/proxy.dat"; + string username = ""; + string password = ""; + if (System.IO.File.Exists(proxyFile)) + { + using (StreamReader sr = new StreamReader(proxyFile)) + { + // ReSharper disable MethodHasAsyncOverload + sr.ReadLine(); + username = sr.ReadLine(); + password = sr.ReadLine(); + // ReSharper enable MethodHasAsyncOverload + } + } + if (username != "" && password != "") + { + var authToken = Encoding.ASCII.GetBytes(username + ":" + password); + client.DefaultRequestHeaders.ProxyAuthorization = new AuthenticationHeaderValue("Basic", + Convert.ToBase64String(authToken)); + } + // END Workaround behind some proxies + + return client; + } + // TODO: Refactor public static async Task HttpGetToMemoryStreamOLD( Uri sourceUri, @@ -298,6 +378,7 @@ await memStream.WriteAsync(buffer, 0, bytesRead, /// Called also for failed status codes! /// Any exception might occur outside the HTTP status codes. public static async Task HttpGetToMemoryStream( + HttpClient reUseClient, Uri sourceUri, Action lambdaDownloadDoneOrFail, PackCntRuntimeOptions runtimeOptions = null, @@ -325,19 +406,8 @@ public static async Task HttpGetToMemoryStream( } } - // - - // read via HttpClient (uses standard proxies) - var handler = new HttpClientHandler(); - handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; - handler.AllowAutoRedirect = false; - - var client = new HttpClient(handler); - - // TODO/CHECK: Basyx does not like this: - // client.DefaultRequestHeaders.Add("Accept", "application/aas"); - client.DefaultRequestHeaders.Add("Accept", "application/json"); - client.BaseAddress = new Uri(sourceUri.GetLeftPart(UriPartial.Authority)); + // client + var client = reUseClient ?? CreateHttpClient(sourceUri, runtimeOptions, containerList); var requestPath = sourceUri.PathAndQuery; // Log @@ -345,49 +415,15 @@ public static async Task HttpGetToMemoryStream( runtimeOptions.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + $"and request {requestPath} .. "); - // Token existing? - var clhttp = containerList as PackageContainerListHttpRestBase; - var oidc = clhttp?.OpenIdClient; - if (oidc == null) - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); - if (clhttp != null && OpenIDClient.email != "") - { - clhttp.OpenIdClient = new OpenIdClientInstance(); - clhttp.OpenIdClient.email = OpenIDClient.email; - clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; - clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; - oidc = clhttp.OpenIdClient; - } - } - if (oidc != null) - { - if (oidc.token != "") - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($" using existing bearer token."); - client.SetBearerToken(oidc.token); - } - else - { - if (oidc.email != "") - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($" using existing email token."); - client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); - } - } - } - bool repeat = true; - while (repeat) { // get response? var response = await client.GetAsync(requestPath, HttpCompletionOption.ResponseHeadersRead); + var clhttp = containerList as PackageContainerListHttpRestBase; + var oidc = clhttp?.OpenIdClient; if (clhttp != null && response.StatusCode == System.Net.HttpStatusCode.TemporaryRedirect) { @@ -745,6 +781,7 @@ await memStream.WriteAsync(buffer, 0, bytesRead, } public static async Task> HttpPutPostFromMemoryStream( + HttpClient reUseClient, MemoryStream ms, Uri destUri, bool usePost = false, @@ -755,14 +792,8 @@ public static async Task> HttpPutPostFromMemoryStr if (ms == null || destUri == null) return null; - // read via HttpClient (uses standard proxies) - var handler = new HttpClientHandler(); - handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; - - // new http client - var client = new HttpClient(handler); - - client.BaseAddress = new Uri(destUri.GetLeftPart(UriPartial.Authority)); + // client + var client = reUseClient ?? CreateHttpClient(destUri, runtimeOptions, containerList); var requestPath = destUri.PathAndQuery; // Log @@ -770,71 +801,7 @@ public static async Task> HttpPutPostFromMemoryStr runtimeOptions.Log?.Info($"HttpClient PUT/POSTZ() with base-address {client.BaseAddress} " + $"and request {requestPath} .. "); - // Token existing? - var clhttp = containerList as PackageContainerListHttpRestBase; - var oidc = clhttp?.OpenIdClient; - if (oidc == null) - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); - if (clhttp != null && OpenIDClient.email != "") - { - clhttp.OpenIdClient = new OpenIdClientInstance(); - clhttp.OpenIdClient.email = OpenIDClient.email; - clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; - clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; - oidc = clhttp.OpenIdClient; - } - } - if (oidc != null) - { - if (oidc.token != "") - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($" using existing bearer token."); - client.SetBearerToken(oidc.token); - } - else - { - if (oidc.email != "") - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($" using existing email token."); - client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); - } - } - } - - // BEGIN Workaround behind some proxies - // Stream is sent twice, if proxy-authorization header is not set - string proxyFile = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "/proxy.dat"; - string username = ""; - string password = ""; - if (System.IO.File.Exists(proxyFile)) - { - using (StreamReader sr = new StreamReader(proxyFile)) - { - // ReSharper disable MethodHasAsyncOverload - sr.ReadLine(); - username = sr.ReadLine(); - password = sr.ReadLine(); - // ReSharper enable MethodHasAsyncOverload - } - } - if (username != "" && password != "") - { - var authToken = Encoding.ASCII.GetBytes(username + ":" + password); - client.DefaultRequestHeaders.ProxyAuthorization = new AuthenticationHeaderValue("Basic", - Convert.ToBase64String(authToken)); - } - // END Workaround behind some proxies - - // make base64 - //var ba = ms.ToArray(); - //var base64 = Convert.ToBase64String(ba); - - // customised HttpContent to track progress - // var data = new ProgressableStreamContent(Encoding.UTF8.GetBytes(base64), runtimeOptions); + // make data var data = new ProgressableStreamContent(ms.ToArray(), runtimeOptions); // get response? @@ -852,7 +819,12 @@ public static async Task> HttpPutPostFromMemoryStr } } + /// + /// Send a DELETE to the client. + /// + /// If not null, re-use client public static async Task> HttpDeleteUri( + HttpClient reUseClient, Uri delUri, PackCntRuntimeOptions runtimeOptions = null, PackageContainerListBase containerList = null) @@ -861,14 +833,8 @@ public static async Task> HttpDeleteUri( if (delUri == null) return null; - // read via HttpClient (uses standard proxies) - var handler = new HttpClientHandler(); - handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; - - // new http client - var client = new HttpClient(handler); - - client.BaseAddress = new Uri(delUri.GetLeftPart(UriPartial.Authority)); + // client + var client = reUseClient ?? CreateHttpClient(delUri, runtimeOptions, containerList); var requestPath = delUri.PathAndQuery; // Log @@ -876,65 +842,6 @@ public static async Task> HttpDeleteUri( runtimeOptions.Log?.Info($"HttpClient DELETE() with base-address {client.BaseAddress} " + $"and request {requestPath} .. "); - // Token existing? - var clhttp = containerList as PackageContainerListHttpRestBase; - var oidc = clhttp?.OpenIdClient; - if (oidc == null) - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); - if (clhttp != null && OpenIDClient.email != "") - { - clhttp.OpenIdClient = new OpenIdClientInstance(); - clhttp.OpenIdClient.email = OpenIDClient.email; - clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; - clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; - oidc = clhttp.OpenIdClient; - } - } - if (oidc != null) - { - if (oidc.token != "") - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($" using existing bearer token."); - client.SetBearerToken(oidc.token); - } - else - { - if (oidc.email != "") - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($" using existing email token."); - client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); - } - } - } - - // BEGIN Workaround behind some proxies - // Stream is sent twice, if proxy-authorization header is not set - string proxyFile = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "/proxy.dat"; - string username = ""; - string password = ""; - if (System.IO.File.Exists(proxyFile)) - { - using (StreamReader sr = new StreamReader(proxyFile)) - { - // ReSharper disable MethodHasAsyncOverload - sr.ReadLine(); - username = sr.ReadLine(); - password = sr.ReadLine(); - // ReSharper enable MethodHasAsyncOverload - } - } - if (username != "" && password != "") - { - var authToken = Encoding.ASCII.GetBytes(username + ":" + password); - client.DefaultRequestHeaders.ProxyAuthorization = new AuthenticationHeaderValue("Basic", - Convert.ToBase64String(authToken)); - } - // END Workaround behind some proxies - // get response? using (var response = await client.DeleteAsync(requestPath)) { @@ -950,6 +857,7 @@ public static async Task> HttpDeleteUri( } public static async Task> HttpPutPostIdentifiable( + HttpClient reUseClient, Aas.IIdentifiable idf, Uri destUri, bool usePost = false, @@ -980,6 +888,7 @@ public static async Task> HttpPutPostIdentifiable( // write return await PackageHttpDownloadUtil.HttpPutPostFromMemoryStream( + reUseClient, ms, destUri: destUri, runtimeOptions: runtimeOptions, @@ -997,6 +906,7 @@ public static async Task> HttpPutPostIdentifiable( /// Type of Identifiable /// Type of entity element public static async Task DownloadListOfIdentifiables( + HttpClient reUseClient, IEnumerable entities, Func lambdaGetLocation, Action lambdaDownloadDoneOrFail, @@ -1041,6 +951,7 @@ public static async Task DownloadListOfIdentifiables( foreach (var ent in entities) { await PackageHttpDownloadUtil.HttpGetToMemoryStream( + reUseClient, sourceUri: lambdaGetLocation?.Invoke(ent), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, @@ -1078,6 +989,7 @@ await Parallel.ForEachAsync(entities, { var thisEnt = ent; await PackageHttpDownloadUtil.HttpGetToMemoryStream( + reUseClient, sourceUri: lambdaGetLocation?.Invoke(ent), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, @@ -1121,6 +1033,7 @@ public static async Task DownloadIdentifiableToOK( T res = default(T); await DownloadListOfIdentifiables( + null, new[] { location }, lambdaGetLocation: (loc) => loc, runtimeOptions: runtimeOptions, @@ -1148,6 +1061,7 @@ public static async Task DownloadEntityToDynamicObject( // GET await HttpGetToMemoryStream( + null, sourceUri: uri, allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, @@ -1176,6 +1090,7 @@ await HttpGetToMemoryStream( } public static async Task DeleteListOfEntities( + HttpClient reUseClient, IEnumerable entities, Func lambdaGetLocation, Action lambdaDeleteDoneOrFail, @@ -1197,6 +1112,7 @@ public static async Task DeleteListOfEntities( { // delete var res = await HttpDeleteUri( + reUseClient, delUri: lambdaGetLocation?.Invoke(ent), runtimeOptions: runtimeOptions); @@ -1212,6 +1128,7 @@ await Parallel.ForEachAsync(entities, { // delete var res = await HttpDeleteUri( + reUseClient, delUri: lambdaGetLocation?.Invoke(ent), runtimeOptions: runtimeOptions); From dc8659e128c73234b866afc05bdc85ee2da7a89e Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Fri, 1 Nov 2024 17:07:07 +0100 Subject: [PATCH 22/99] * update before refactore --- src/AasxPackageExplorer/MainWindow.xaml.cs | 2 +- src/AasxPackageExplorer/debug.MIHO.script | 10 +- .../DispEditHelperEntities.cs | 25 +- .../AdminShellPackageDynamicFetchEnv.cs | 7 +- .../PackageContainerHttpRepoSubset.cs | 392 +++++++++++++----- .../PackageCentral/PackageHttpDownloadUtil.cs | 264 +----------- .../Flyouts/SelectFromDataGridFlyout.xaml.cs | 2 +- 7 files changed, 316 insertions(+), 386 deletions(-) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 6ca925a61..42e86f16e 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -2662,7 +2662,7 @@ private async Task MainTimer_Tick(object sender, EventArgs e) // flags for tainted Identifiables MainTimer_CheckTaintedIdentifiables( _mainTimer_LastCheckForTaintedIdentifiables, - PackageCentral.MainItem.Container.Env?.AasEnv); + PackageCentral.MainItem.Container?.Env?.AasEnv); _mainTimer_LastCheckForTaintedIdentifiables = DateTime.UtcNow; // pending re-index? diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index aeac2fc2b..a52a6a382 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,13 +9,9 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -// Tool("connectextended", -// "BaseAddress", "https://eis-data.aas-voyager.com/", -// "BaseType", "Repository", -// "GetSingleAas", "false", -// "GetAllAas", "true", -// "PageLimit", "2", -// "AutoLoadOnDemand", "false"); +// Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false"); +// Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); +// Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); // Tool("apiuploadassistant"); // Select("Submodel", "First"); diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 8e77d9aaa..c71d3936a 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -1553,7 +1553,7 @@ public static async Task ExecuteUiForFetchOfElements( { MainWindowLogic.LogErrorToTicketStatic(ticket, new InvalidDataException(), - "Error building location from next fetch selection. Aborting."); + "Error building location from fetch selection. Aborting."); return false; } @@ -2128,9 +2128,11 @@ public void DisplayOrEditAasEntitySubmodelOrRef( AddActionPanel(stack, "Submodel:", repo: repo, superMenu: superMenu, ticketMenu: new AasxMenu() - .AddAction("aas-elem-del", "Delete", - "Deletes the currently selected element.", - inputGesture: "Ctrl+Shift+Delete"), + .AddAction("aas-elem-del", "Delete \U0001f847 here", + "Deletes the currently selected Submodel in the local environment.", + inputGesture: "Ctrl+Shift+Delete") + .AddAction("delete-sm-in-repo", "Delete SM \u274c in Repo", + "Delete Submodel by Id in a given Repository or Registry."), ticketAction: (buttonNdx, ticket) => { if (buttonNdx == 0) @@ -2314,7 +2316,7 @@ public void DisplayOrEditAasEntitySubmodelOrRef( "assigns the SubmodelElement to it.") .AddAction("create-smes", "Create \U0001f844 SMEs (all)", "Create missing CDs from semanticId of used SMEs.") - .AddAction("delete-cd-in-repo", "Delete \u274c in Repo", + .AddAction("delete-cd-in-repo", "Delete CD \u274c in Repo", "Delete ConceptDescriptions which are referenced by semanticId of SubmodelElements " + "in a given Repository or Registry."), ticketActionAsync: async (buttonNdx, ticket) => @@ -2390,11 +2392,22 @@ public void DisplayOrEditAasEntitySubmodelOrRef( var cdIds = lrs.Select((lr) => lr?.Reference?.GetAsExactlyOneKey()?.Value); // call function + // (only the side info in the _specific_ endpoint gives information, in which + // repo the CDs could be deleted) await PackageContainerHttpRepoSubset.AssistantDeleteCDsInRepo( ticket, context, "Delete CDs in Repository/ Registry", cdIds, - runtimeOptions: packages.CentralRuntimeOptions); + runtimeOptions: packages.CentralRuntimeOptions, + presetRecord: new PackageContainerHttpRepoSubset.DeleteAssistantJobRecord() + { + // assume Repo ?! + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + + // extract base address + BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo?.Endpoint?.AbsoluteUri)?.AbsoluteUri + }); // ok return new AnyUiLambdaActionNone(); diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index c2461d17e..3ec5f9c3e 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -84,11 +84,14 @@ public async Task TryFetchThumbnail(Aas.IAssetAdministrationShell aas) // try try { - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + null, sourceUri: PackageContainerHttpRepoSubset.BuildUriForRepoAasThumbnail(_defaultRepoBaseUri, aas.Id), runtimeOptions: _runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + if (code != HttpStatusCode.OK) + return; AddThumbnail(aas.Id, ms.ToArray()); }); diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index d0b5a7578..8c2c36e2e 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -242,6 +242,10 @@ public static bool IsValidUriAnyMatch(string location) public static Uri GetBaseUri(string location) { + // access + if (location?.HasContent() != true) + return null; + // try an explicit search for known parts of ressources // (preserves scheme, host and leading pathes) var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/conceptdescription|/lookup)"); @@ -357,12 +361,19 @@ public static Uri BuildUriForRepoAllSubmodel(Uri baseUri, int pageLimit = 100, s public static Uri BuildUriForRepoSingleSubmodel( Uri baseUri, string id, bool encryptIds = true, - bool usePost = false) + bool usePost = false, + bool addAasId = false, + string aasId = null) { // access if (id?.HasContent() != true) return null; + // query string for aasId? + var queryAasId = ""; + if (addAasId && aasId?.HasContent() == true) + queryAasId = "?aasIdentifier=" + aasId; + // try combine if (!usePost) { @@ -371,7 +382,7 @@ public static Uri BuildUriForRepoSingleSubmodel( } else { - return CombineUri(baseUri, "submodels"); + return CombineUri(baseUri, "submodels" + queryAasId); } } @@ -666,6 +677,8 @@ public override async Task LoadFromSourceAsync( { var allowFakeResponses = runtimeOptions?.AllowFakeResponses ?? false; + PackageContainerListBase containerList = null; + var baseUri = GetBaseUri(fullItemLocation); // for the time being, make sure, we have the correct list implementations @@ -701,25 +714,42 @@ public override async Task LoadFromSourceAsync( if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) { - // AAS descriptors? - var isAllAas = IsValidUriForRegistryAllAAS(fullItemLocation); - var isAasByAssetId = IsValidUriForRegistryAasByAssetId(fullItemLocation); - if (isAllAas || isAasByAssetId) + // All AAS descriptors? + if (IsValidUriForRegistryAllAAS(fullItemLocation)) { // ok operationFound = true; - // prepare receing the descriptors + // prepare receiving the descriptors var resObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( new Uri(fullItemLocation), runtimeOptions, allowFakeResponses); - // Note: the result format for GetAllAAS and GetAllAssetAdministrationShellIdsByAssetLink - // is diffeent! - var arrObj = resObj; - if (isAllAas) - arrObj = resObj?.result; + // have directly a list of descriptors?! + if (resObj?.result == null) + { + runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + return; + } + + foreach (var res in resObj.result) + { + // refer to dedicated function + await FromRegistryGetAasAndSubmodels( + prepAas, prepSM, record, runtimeOptions, allowFakeResponses, res); + } + } + + // Asset Link + if (IsValidUriForRegistryAasByAssetId(fullItemLocation)) + { + // ok + operationFound = true; + + // prepare receiving the descriptors + var resObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( + new Uri(fullItemLocation), runtimeOptions, allowFakeResponses); - // have a list of descriptors?! + // Note: GetAllAssetAdministrationShellIdsByAssetLink only returns a list of ids if (resObj == null) { runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); @@ -728,12 +758,15 @@ public override async Task LoadFromSourceAsync( // Have a list of ids. Decompose into single id. // Note: Parallel makes no sense, ideally only 1 result (is per AssetId)!! + var noRes = true; foreach (var res in resObj) { + noRes = false; + // in res, have only an id. Get the descriptor var id = "" + res; var singleDesc = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( - BuildUriForRegistrySingleAAS(baseUri, id, encryptIds: true), + BuildUriForRegistrySingleAAS(baseUri, id, encryptIds: true), runtimeOptions, allowFakeResponses); if (singleDesc == null || !HasProperty(singleDesc, "endpoints")) continue; @@ -742,6 +775,13 @@ public override async Task LoadFromSourceAsync( await FromRegistryGetAasAndSubmodels( prepAas, prepSM, record, runtimeOptions, allowFakeResponses, singleDesc); } + + // check again (count) + if (noRes) + { + runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + return; + } } // start with single AAS? @@ -804,6 +844,9 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { + // for all repo access, use the same client + var client = PackageHttpDownloadUtil.CreateHttpClient(baseUri, runtimeOptions, containerList); + // start with a list of AAS or Submodels (very similar) var isAllAAS = IsValidUriForRepoAllAAS(fullItemLocation); var isAllSM = IsValidUriForRepoAllSubmodel(fullItemLocation); @@ -812,12 +855,16 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // ok operationFound = true; - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + client, sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + if (code != HttpStatusCode.OK) + return; + try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); @@ -831,42 +878,52 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // second try to reduce side effects try { + // skip if (childsToSkip > 0) { childsToSkip--; continue; } + // get identifiable + Aas.IIdentifiable idf = null; + if (isAllAAS) + idf = Jsonization.Deserialize.AssetAdministrationShellFrom(n2); + if (isAllSM) + idf = Jsonization.Deserialize.SubmodelFrom(n2); + if (idf == null) + continue; + // on last child, attach side info for fetch prev/ next cursor - AasIdentifiableSideInfo si = null; + AasIdentifiableSideInfo si = new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = idf.Id, + IdShort = idf.IdShort, + Endpoint = new Uri(fullItemLocation) + }; if (firstNonSkipped && record.PageOffset > 0) - si = new AasIdentifiableSideInfo() - { - IsStub = false, - ShowCursorAbove = true - }; - firstNonSkipped = false; + si.ShowCursorAbove = true; if (n2 == resChilds.Last() && record.PageLimit > 0) - si = new AasIdentifiableSideInfo() - { - IsStub = false, - ShowCursorBelow = true - }; + si.ShowCursorBelow = true; + + firstNonSkipped = false; // add if (isAllAAS) prepAas.Add( - Jsonization.Deserialize.AssetAdministrationShellFrom(n2), + idf as Aas.IAssetAdministrationShell, si); if (isAllSM) prepSM.Add( - Jsonization.Deserialize.SubmodelFrom(n2), + idf as Aas.ISubmodel, si); } catch (Exception ex) { - runtimeOptions?.Log?.Error(ex, "Parsing single AAS of list of all AAS"); + runtimeOptions?.Log?.Error(ex, "Parsing single AAS/ Submodel of list of all AAS/ Submodel"); } } @@ -890,16 +947,27 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // ok operationFound = true; - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + client, sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + if (code != HttpStatusCode.OK) + return; + try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - prepAas.Add(Jsonization.Deserialize.AssetAdministrationShellFrom(node), null); + var aas = Jsonization.Deserialize.AssetAdministrationShellFrom(node); + prepAas.Add(aas, new AasIdentifiableSideInfo() { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = aas.Id, + IdShort = aas.IdShort, + Endpoint = new Uri(fullItemLocation) + }); } catch (Exception ex) { @@ -914,16 +982,28 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // ok operationFound = true; - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + client, sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + if (code != HttpStatusCode.OK) + return; + try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); + var sm = Jsonization.Deserialize.SubmodelFrom(node); + prepSM.Add(sm, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = sm.Id, + IdShort = sm.IdShort, + Endpoint = new Uri(fullItemLocation) + }); } catch (Exception ex) { @@ -938,16 +1018,28 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // ok operationFound = true; - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + client, sourceUri: new Uri(fullItemLocation), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + if (code != HttpStatusCode.OK) + return; + try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); - prepCD.Add(Jsonization.Deserialize.ConceptDescriptionFrom(node), null); + var cd = Jsonization.Deserialize.ConceptDescriptionFrom(node); + prepCD.Add(cd, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = cd.Id, + IdShort = cd.IdShort, + Endpoint = new Uri(fullItemLocation) + }); } catch (Exception ex) { @@ -998,10 +1090,10 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // HTTP POST var statCode = await PackageHttpDownloadUtil.HttpPostRequestToMemoryStream( + client, sourceUri: new Uri(quri.GetLeftPart(UriPartial.Path)), requestBody: jsonQuery, requestContentType: "application/json", - allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, lambdaDownloadDone: (ms, contentFn) => { @@ -1074,17 +1166,29 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( // download (and skip errors) try { - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + null, sourceUri: loc, allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + if (code != HttpStatusCode.OK) + return; + try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + var sm = Jsonization.Deserialize.SubmodelFrom(node); if (fi.Type == FetchItemType.SmUrl || fi.Type == FetchItemType.SmId) - prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); + prepSM.Add(sm, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = sm.Id, + IdShort = sm.IdShort, + Endpoint = new Uri(fullItemLocation) + }); } catch (Exception ex) { @@ -1129,18 +1233,30 @@ await Parallel.ForEachAsync(lrs, else { // no side info => full element - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + null, sourceUri: BuildUriForRepoSingleSubmodel(baseUri, lr.Reference), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + if (code != HttpStatusCode.OK) + return; + try { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + var sm = Jsonization.Deserialize.SubmodelFrom(node); lock (prepSM) { - prepSM.Add(Jsonization.Deserialize.SubmodelFrom(node), null); + prepSM.Add(sm, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = sm.Id, + IdShort = sm.IdShort, + Endpoint = new Uri(fullItemLocation) + }); } } catch (Exception ex) @@ -1158,12 +1274,16 @@ await Parallel.ForEachAsync(env.AllAssetAdministrationShells(), new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, async (aas, token) => { - await PackageHttpDownloadUtil.HttpGetToMemoryStreamOLD( + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + client, sourceUri: BuildUriForRepoAasThumbnail(baseUri, aas.Id), allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, - lambdaDownloadDone: (ms, contentFn) => + lambdaDownloadDoneOrFail: (code, ms, contentFn) => { + if (code != HttpStatusCode.OK) + return; + try { dynPack.AddThumbnail(aas.Id, ms.ToArray()); @@ -1254,9 +1374,9 @@ public enum BaseTypeEnum { Repository, Registry } // public string BaseAddress = "http://smt-repo.admin-shell-io.com/api/v3.0"; // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; - // public BaseTypeEnum BaseType = BaseTypeEnum.Repository; [AasxMenuArgument(help: "Either: Repository or Registry")] - public BaseTypeEnum BaseType = BaseTypeEnum.Registry; + public BaseTypeEnum BaseType = BaseTypeEnum.Repository; + // public BaseTypeEnum BaseType = BaseTypeEnum.Registry; [AasxMenuArgument(help: "Retrieve all AAS from Repository or Registry. " + "Note: Use of PageLimit is recommended.")] @@ -1272,6 +1392,8 @@ public enum BaseTypeEnum { Repository, Registry } [AasxMenuArgument(help: "Get a single AAS, which is specified by a asset link/ asset id.")] public bool GetAasByAssetLink; + + [AasxMenuArgument(help: "Specicies the Id of the asset to be retrieved.")] public string AssetId = ""; // public string AssetId = "https://pk.harting.com/?.20P=ZSN1"; @@ -1929,7 +2051,7 @@ public static async Task PerformConnectExtendedDialogue( return true; } - protected class UploadAssistantJobRecord + public class UploadAssistantJobRecord { // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; public string BaseAddress = "https://eis-data.aas-voyager.com/"; @@ -1942,7 +2064,7 @@ protected class UploadAssistantJobRecord public bool IncludeSubmodels = false; public bool IncludeCDs = false; - public bool OverwriteIfExist = true; + public bool OverwriteIfExist = false; } public static async Task PerformUploadAssistant( @@ -2008,7 +2130,6 @@ public static async Task PerformUploadAssistant( ucJob.ActivateRenderPanel(recordJob, disableScrollArea: false, dialogButtons: AnyUiMessageBoxButton.OK, - // extraButtons: new[] { "A", "B" }, renderPanel: (uci) => { // create panel @@ -2257,7 +2378,7 @@ public static async Task PerformUploadAssistant( maxWidth: 9999); ucSelect.ColumnDefs = AnyUiListOfGridLength.Parse(new[] { "50:", "1*", "3*", "70:", "70:", "70:" }); - ucSelect.ColumnHeaders = new[] { "Type", "IdShort", "Id", "V.Local", "Exist?", "V.Server" }; + ucSelect.ColumnHeaders = new[] { "Type", "IdShort", "Id", "V.Local", "Action", "V.Server" }; ucSelect.Rows = rows.ToList(); ucSelect.EmptySelectOk = true; @@ -2292,12 +2413,11 @@ public static async Task PerformUploadAssistant( // ask server if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { - // make a sequential upload, first - foreach (var row in rowsToUpload) + Func lambdaRow = async (row) => { // idf? if (!(row?.Tag is Aas.IIdentifiable idf)) - continue; + return; // put / post var usePut = row.Cells != null && row.Cells.Count >= 6 && @@ -2306,7 +2426,16 @@ public static async Task PerformUploadAssistant( row.Cells[4].StartsWith("POST"); if (!usePut && !usePost) - continue; + return; + + // workaround: API for POST Submodel seems to require Aas.Id + // TODO (MIHO, 2024-11-01): follow up, if this is really required! + string aasId = null; + if (idf is Aas.ISubmodel) + { + var aas = packEnv?.AasEnv?.FindAasWithSubmodelId(idf.Id); + aasId = aas?.Id; + } // location Uri location = null; @@ -2317,7 +2446,7 @@ public static async Task PerformUploadAssistant( if (idf is Aas.IConceptDescription) location = BuildUriForRepoSingleCD(baseUri, idf?.Id, encryptIds: true, usePost: usePost); if (location == null) - continue; + return; var res2 = await PackageHttpDownloadUtil.HttpPutPostIdentifiable( client, @@ -2327,27 +2456,49 @@ public static async Task PerformUploadAssistant( runtimeOptions: runtimeOptions, containerList: containerList); - Action lambdaStat = (ok) => + // mutex (after all async calls!) + lock (rowsToUpload) { - if (ok) { numOK++; } else { numNOK++; }; - ucProgUpload.Info = $"{numOK} entities OK, {numNOK} entities NOT OK of {numTotal}\n" + - $"Id: {idf?.Id}"; - ucProgUpload.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); - }; + Action lambdaStat = (ok) => + { + if (ok) { numOK++; } else { numNOK++; }; + ucProgUpload.Info = $"{numOK} entities OK, {numNOK} entities NOT OK of {numTotal}\n" + + $"Id: {idf?.Id}"; + ucProgUpload.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); + }; - if (res2 != null - && (res2.Item1 == HttpStatusCode.Created || res2.Item1 == HttpStatusCode.NoContent)) - { - lambdaStat(true); - } - else - { - runtimeOptions?.Log?.Error("Put/Post of modified Identifiable returned error {0} for id={1} at {2}", - "" + ((res2 != null) ? (int)res2.Item1 : -1), - idf.Id, - location.ToString()); - lambdaStat(false); + if (res2 != null + && (res2.Item1 == HttpStatusCode.Created || res2.Item1 == HttpStatusCode.NoContent)) + { + lambdaStat(true); + } + else + { + runtimeOptions?.Log?.Error("Put/Post of modified Identifiable returned error {0} for id={1} at {2}", + "" + ((res2 != null) ? (int)res2.Item1 : -1), + idf.Id, + location.ToString()); + lambdaStat(false); + } } + }; + + // simple or parallel? + // TODO: currently suspecting aasx server to be not thread safe (error 500?) + if (true || Options.Curr.MaxParallelOps <= 1) + { + // simple to debug + foreach (var row in rowsToUpload) + await lambdaRow(row); + } + else + { + await Parallel.ForEachAsync(rowsToUpload, + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (row, token) => + { + await lambdaRow(row); + }); } } @@ -2366,6 +2517,11 @@ public static async Task PerformUploadAssistant( "while {1} uploaded ok. Location: {2}", numNOK, numOK, recordJob.BaseAddress); } + else if (numOK < 1) + { + runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, "No need of Put/ Push element(s) " + + "to Registry/ Repository. Location {0}", recordJob.BaseAddress); + } else { runtimeOptions?.Log?.Info(StoredPrint.Color.Blue, "Successful Put/ Push of {0} element(s) " + @@ -2377,10 +2533,11 @@ public static async Task PerformUploadAssistant( return true; } - protected class DeleteAssistantJobRecord + public class DeleteAssistantJobRecord { // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; - public string BaseAddress = "https://eis-data.aas-voyager.com/"; + public string BaseAddress = ""; + // public string BaseAddress = "https://eis-data.aas-voyager.com/"; // public string BaseAddress = "http://smt-repo.admin-shell-io.com/api/v3.0"; // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; @@ -2394,7 +2551,8 @@ public static async Task AssistantDeleteCDsInRepo( string caption, IEnumerable cdIds, PackCntRuntimeOptions runtimeOptions = null, - PackageContainerListBase containerList = null) + PackageContainerListBase containerList = null, + DeleteAssistantJobRecord presetRecord = null) { // access if (displayContext == null || caption?.HasContent() != true || cdIds == null || cdIds.Count() < 1) @@ -2406,7 +2564,7 @@ public static async Task AssistantDeleteCDsInRepo( // Screen 1 : ask for job / Repo // - var recordJob = new DeleteAssistantJobRecord(); + var recordJob = presetRecord ?? new DeleteAssistantJobRecord(); var ucJob = new AnyUiDialogueDataModalPanel(caption); ucJob.ActivateRenderPanel(recordJob, disableScrollArea: false, @@ -2517,25 +2675,29 @@ public static async Task AssistantDeleteCDsInRepo( useParallel: Options.Curr.MaxParallelOps > 1, lambdaDownloadDoneOrFail: (code, idf, contentFn, id) => { - // stat - Action lambdaStat = (ok) => + // need mutex + lock (cdExist) { - if (ok) { numOK++; } else { numNOK++; }; - ucProgTest.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + - $"Id: {idf?.Id}"; - ucProgTest.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); - }; + // stat + Action lambdaStat = (ok) => + { + if (ok) { numOK++; } else { numNOK++; }; + ucProgTest.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + + $"Id: {idf?.Id}"; + ucProgTest.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); + }; + + // any error? + if (code != HttpStatusCode.OK || idf == null) + { + lambdaStat(false); + return; + } - // any error? - if (code != HttpStatusCode.OK || idf == null) - { - lambdaStat(false); - return; + // remember for later + cdExist.Add(idf); + lambdaStat(true); } - - // remember for later - cdExist.Add(idf); - lambdaStat(true); }); } @@ -2602,24 +2764,28 @@ public static async Task AssistantDeleteCDsInRepo( useParallel: Options.Curr.MaxParallelOps > 1, lambdaDeleteDoneOrFail: (code, content, cd2) => { - // stat - Action lambdaStat = (ok) => + // need mutex + lock (cdExist) { - if (ok) { numOK++; } else { numNOK++; }; - ucProgDel.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + - $"Id: {cd2?.Id}"; - ucProgDel.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); - }; + // stat + Action lambdaStat = (ok) => + { + if (ok) { numOK++; } else { numNOK++; }; + ucProgDel.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + + $"Id: {cd2?.Id}"; + ucProgDel.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); + }; + + // any error? + if (code != HttpStatusCode.OK && code != HttpStatusCode.NoContent) + { + lambdaStat(false); + return; + } - // any error? - if (code != HttpStatusCode.OK && code != HttpStatusCode.NoContent) - { - lambdaStat(false); - return; + // ok + lambdaStat(true); } - - // ok - lambdaStat(true); }); } diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index 7dc21f702..e59c49a65 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -164,214 +164,6 @@ public static HttpClient CreateHttpClient( return client; } - // TODO: Refactor - public static async Task HttpGetToMemoryStreamOLD( - Uri sourceUri, - Action lambdaDownloadDone, - PackCntRuntimeOptions runtimeOptions = null, - PackageContainerListBase containerList = null, - bool allowFakeResponses = false) - { - // access - if (sourceUri == null) - return; - - // check for fake answers? - if (allowFakeResponses && _fakeAnswers?.Http != null) - foreach (var fa in _fakeAnswers.Http) - if (fa.Request.Equals(sourceUri.ToString(), StringComparison.InvariantCultureIgnoreCase)) - { - using (var memStream = new MemoryStream()) - using (var writer = new StreamWriter(memStream)) - { - writer.Write(AdminShellUtil.Base64Decode(fa.Response)); - writer.Flush(); - memStream.Flush(); - memStream.Seek(0, SeekOrigin.Begin); - lambdaDownloadDone?.Invoke(memStream, "content.json"); - return; - } - } - - // read via HttpClient (uses standard proxies) - var handler = new HttpClientHandler(); - handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; - handler.AllowAutoRedirect = false; - - var client = new HttpClient(handler); - - // TODO/CHECK: Basyx does not like this: - // client.DefaultRequestHeaders.Add("Accept", "application/aas"); - client.DefaultRequestHeaders.Add("Accept", "application/json"); - client.BaseAddress = new Uri(sourceUri.GetLeftPart(UriPartial.Authority)); - var requestPath = sourceUri.PathAndQuery; - - // Log - runtimeOptions?.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + - $"and request {requestPath} .. "); - - // Token existing? - var clhttp = containerList as PackageContainerListHttpRestBase; - var oidc = clhttp?.OpenIdClient; - if (oidc == null) - { - runtimeOptions?.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); - if (clhttp != null && OpenIDClient.email != "") - { - clhttp.OpenIdClient = new OpenIdClientInstance(); - clhttp.OpenIdClient.email = OpenIDClient.email; - clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; - clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; - oidc = clhttp.OpenIdClient; - } - } - if (oidc != null) - { - if (oidc.token != "") - { - runtimeOptions?.Log?.Info($" using existing bearer token."); - client.SetBearerToken(oidc.token); - } - else - { - if (oidc.email != "") - { - runtimeOptions?.Log?.Info($" using existing email token."); - client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); - } - } - } - - bool repeat = true; - - while (repeat) - { - // get response? - var response = await client.GetAsync(requestPath, - HttpCompletionOption.ResponseHeadersRead); - - if (clhttp != null - && response.StatusCode == System.Net.HttpStatusCode.TemporaryRedirect) - { - string redirectUrl = response.Headers.Location.ToString(); - // ReSharper disable once RedundantExplicitArrayCreation - string[] splitResult = redirectUrl.Split(new string[] { "?" }, - StringSplitOptions.RemoveEmptyEntries); - splitResult[0] = splitResult[0].TrimEnd('/'); - - if (splitResult.Length < 1) - { - runtimeOptions?.Log?.Error("TemporaryRedirect, but url split to successful"); - break; - } - - runtimeOptions?.Log?.Info("Redirect to: " + splitResult[0]); - - if (oidc == null) - { - runtimeOptions?.Log?.Info("Creating new OpenIdClient.."); - oidc = new OpenIdClientInstance(); - clhttp.OpenIdClient = oidc; - clhttp.OpenIdClient.email = OpenIDClient.email; - clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; - clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; - } - - oidc.authServer = splitResult[0]; - - runtimeOptions?.Log?.Info($".. authentication at auth server {oidc.authServer} needed"); - - var response2 = await oidc.RequestTokenAsync(null, - GenerateUiLambdaSet(runtimeOptions)); - if (oidc.keycloak == "" && response2 != null) - oidc.token = response2.AccessToken; - if (oidc.token != "" && oidc.token != null) - client.SetBearerToken(oidc.token); - - repeat = true; - continue; - } - - repeat = false; - - if (response.IsSuccessStatusCode) - { - var contentLength = response.Content.Headers.ContentLength; - var contentFn = response.Content.Headers.ContentDisposition?.FileName; - - // log - runtimeOptions?.Log?.Info($".. response with header-content-len {contentLength} " + - $"and file-name {contentFn} .."); - - var contentStream = await response?.Content?.ReadAsStreamAsync(); - if (contentStream == null) - throw new PackageContainerException( - $"While getting data bytes from {sourceUri.ToString()} via HttpClient " + - $"no data-content was responded!"); - - // create temp file and write to it - var givenFn = sourceUri.ToString(); - if (contentFn != null) - givenFn = contentFn; - runtimeOptions?.Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " + - $"and request {requestPath} .. "); - - using (var memStream = new MemoryStream()) - { - // copy with progress - var bufferSize = 4024; - var deltaSize = 512 * 1024; - var buffer = new byte[bufferSize]; - long totalBytesRead = 0; - long lastBytesRead = 0; - int bytesRead; - - runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Starting, - contentLength, totalBytesRead); - - while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, - default(CancellationToken)).ConfigureAwait(false)) != 0) - { - await memStream.WriteAsync(buffer, 0, bytesRead, - default(CancellationToken)).ConfigureAwait(false); - - totalBytesRead += bytesRead; - - if (totalBytesRead > lastBytesRead + deltaSize) - { - runtimeOptions?.Log?.Info($".. downloading to memory stream"); - runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Ongoing, - contentLength, totalBytesRead); - lastBytesRead = totalBytesRead; - } - } - - // assume bytes read to be total bytes - runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.Final, - totalBytesRead, totalBytesRead); - - // log - runtimeOptions?.Log?.Info($".. download done with {totalBytesRead} bytes read!"); - - // execute lambda - memStream.Flush(); - memStream.Seek(0, SeekOrigin.Begin); - lambdaDownloadDone?.Invoke(memStream, contentFn); - } - } - else - if (response.StatusCode == HttpStatusCode.NotFound) - { - // not found but also no error: intentionally do nothing - } - else - { - Log.Singleton.Error($"DownloadFromSource server gave status code {response.StatusCode}!"); - throw new PackageContainerException($"Unsuccessfull status code {response.StatusCode}"); - } - } - } - /// /// Try get an HTTP ressource by GET. /// @@ -554,69 +346,27 @@ await memStream.WriteAsync(buffer, 0, bytesRead, } public static async Task HttpPostRequestToMemoryStream( + HttpClient reUseClient, Uri sourceUri, string requestContentType, string requestBody, Action lambdaDownloadDone, PackCntRuntimeOptions runtimeOptions = null, - PackageContainerListBase containerList = null, - bool allowFakeResponses = false) + PackageContainerListBase containerList = null) { // access if (sourceUri == null) return null; - // read via HttpClient (uses standard proxies) - var handler = new HttpClientHandler(); - handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; - handler.AllowAutoRedirect = false; - - var client = new HttpClient(handler); - - // client.DefaultRequestHeaders.Add("Accept", "application/json"); - client.BaseAddress = new Uri(sourceUri.GetLeftPart(UriPartial.Authority)); + // client + var client = reUseClient ?? CreateHttpClient(sourceUri, runtimeOptions, containerList); var requestPath = sourceUri.PathAndQuery; // Log if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($"HttpClient GET() with base-address {client.BaseAddress} " + + runtimeOptions.Log?.Info($"HttpClient POST() request with base-address {client.BaseAddress} " + $"and request {requestPath} .. "); - // Token existing? - var clhttp = containerList as PackageContainerListHttpRestBase; - var oidc = clhttp?.OpenIdClient; - if (oidc == null) - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info(" no ContainerList available. No OpecIdClient possible!"); - if (clhttp != null && OpenIDClient.email != "") - { - clhttp.OpenIdClient = new OpenIdClientInstance(); - clhttp.OpenIdClient.email = OpenIDClient.email; - clhttp.OpenIdClient.ssiURL = OpenIDClient.ssiURL; - clhttp.OpenIdClient.keycloak = OpenIDClient.keycloak; - oidc = clhttp.OpenIdClient; - } - } - if (oidc != null) - { - if (oidc.token != "") - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($" using existing bearer token."); - client.SetBearerToken(oidc.token); - } - else - { - if (oidc.email != "") - { - if (runtimeOptions?.ExtendedConnectionDebug == true) - runtimeOptions.Log?.Info($" using existing email token."); - client.DefaultRequestHeaders.Add("Email", OpenIDClient.email); - } - } - } - // prepare request var requestContent = new StringContent(requestBody, Encoding.UTF8, requestContentType); @@ -626,8 +376,10 @@ await memStream.WriteAsync(buffer, 0, bytesRead, while (repeat) { // get response? - var response = await client.PostAsync(requestPath, requestContent); + var response = await client.PostAsync(requestPath, requestContent); + var clhttp = containerList as PackageContainerListHttpRestBase; + var oidc = clhttp?.OpenIdClient; if (clhttp != null && response.StatusCode == System.Net.HttpStatusCode.TemporaryRedirect) { diff --git a/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml.cs b/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml.cs index f5b80e895..36d1d5fd5 100644 --- a/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml.cs +++ b/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml.cs @@ -148,7 +148,7 @@ private void ButtonSelect_Click(object sender, RoutedEventArgs e) DiaData.ButtonIndex = bi; // give result - if (DiaData.EmptySelectOk || PrepareResult()) + if (PrepareResult() || DiaData.EmptySelectOk) { DiaData.Result = true; ControlClosed?.Invoke(); From d60e69ee1dc644d4579c3195df3ea321b8d7b2e8 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sat, 2 Nov 2024 14:14:51 +0100 Subject: [PATCH 23/99] * DELETE of resources in repo * fix (updated) tainted saving --- src/AasxPackageExplorer/debug.MIHO.script | 2 +- .../options-debug.MIHO.json | 8 + src/AasxPackageLogic/DispEditHelperBasics.cs | 19 +- .../DispEditHelperEntities.cs | 170 ++++++++++++- .../DispEditHelperMultiElement.cs | 103 ++++++++ src/AasxPackageLogic/ExplorerMenuFactory.cs | 1 + .../MainWindowAnyUiDialogs.cs | 29 +++ src/AasxPackageLogic/Options.cs | 5 + .../AdminShellPackageDynamicFetchEnv.cs | 10 +- .../PackageContainerHttpRepoSubset.cs | 230 ++++++++++++------ .../ImageMapAnyUiControl.cs | 10 +- 11 files changed, 503 insertions(+), 84 deletions(-) diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index a52a6a382..c3bf35e81 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,7 +9,7 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -// Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false"); +Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); // Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 7b38417c3..b999cc3f7 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -104,6 +104,14 @@ "BackupDir": ".\\backup", "BackupFiles": 10, "MaxParallelOps": 1, + "BaseAddresses": [ + "https://cloudrepo.aas-voyager.com/", + "https://eis-data.aas-voyager.com/", + "http://smt-repo.admin-shell-io.com/api/v3.0", + "https://techday2-registry.admin-shell-io.com/", + "http://localhost:5001/", + "http://localhost:5001/api/v3.0/" + ], "ExtendedConnectionDebug": false, "AllowFakeResponses": true, "RestServerHost": "localhost", diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index b9e45c2ed..b10cd585c 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -2470,7 +2470,10 @@ public void EntityListUpDownDeleteHelper( object nextFocus = null, PackCntChangeEventData sendUpdateEvent = null, bool preventMove = false, Aas.IReferable explicitParent = null, AasxMenu superMenu = null, - Action postActionHook = null) + Action postActionHook = null, + AasxMenu extraMenu = null, + Func lambdaExtraMenu = null, + Func> lambdaExtraMenuAsync = null) { if (nextFocus == null) nextFocus = entity; @@ -2499,12 +2502,15 @@ public void EntityListUpDownDeleteHelper( "Deletes the currently selected element.", inputGesture: "Ctrl+Shift+Delete"); + if (extraMenu != null) + theMenu.AddRange(extraMenu); + AddActionPanel( stack, label, repo: repo, superMenu: superMenu, ticketMenu: theMenu, - ticketAction: (buttonNdx, ticket) => + ticketActionAsync: async (buttonNdx, ticket) => { if (buttonNdx >= 0 && buttonNdx <= 3) { @@ -2573,6 +2579,15 @@ public void EntityListUpDownDeleteHelper( return new AnyUiLambdaActionRedrawAllElements(nextFocus: ret, isExpanded: null); } + if (buttonNdx > 4) + { + // invoke extra menu + if (lambdaExtraMenu != null) + return lambdaExtraMenu?.Invoke(buttonNdx - 5); + if (lambdaExtraMenuAsync != null) + return await lambdaExtraMenuAsync?.Invoke(buttonNdx - 5); + } + return new AnyUiLambdaActionNone(); }); } diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index c71d3936a..3c28bd48c 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -1621,14 +1621,58 @@ public void DisplayOrEditAasEntityAas( // // main group - this.AddGroup(stack, "Editing of entities", this.levelColors.MainSection); + this.AddGroup(stack, "Editing of entities", this.levelColors.SubSection); // Up/ down/ del this.EntityListUpDownDeleteHelper( stack, repo, env.AssetAdministrationShells, (lst) => { env.AssetAdministrationShells = lst; }, aas, env, "AAS:", - superMenu: superMenu); + superMenu: superMenu, + extraMenu: new AasxMenu() + .AddAction("delete-aas-in-repo", "Delete AAS \u274c in Repo", + "Delete AAS by Id in a given Repository or Registry."), + lambdaExtraMenuAsync: async (buttonNdx) => + { + if (buttonNdx == 0) + { + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.AssetAdministrationShells, aas.GetReference()); + + // simply prepare some Keys! + var idfKeys = (new Aas.IKey[] { + new Aas.Key(KeyTypes.AssetAdministrationShell, "" + aas.Id) }).ToList(); + foreach (var smr in aas.Submodels.ForEachSafe()) + if (smr?.IsValid() == true) + idfKeys.Add(new Aas.Key(KeyTypes.Submodel, smr.Keys.First().Value)); + + // call function + // (only the side info in the _specific_ endpoint gives information, in which + // repo the Indentifiables could be deleted) + await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( + null, context, + "Delete AAS and Submodels in Repository/ Registry", + "AAS and Submodel", + idfKeys, + runtimeOptions: packages.CentralRuntimeOptions, + presetRecord: new PackageContainerHttpRepoSubset.DeleteAssistantJobRecord() + { + // assume Repo ?! + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + + // extract base address + BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo?.Endpoint?.AbsoluteUri)?.AbsoluteUri + }); + + // ok + return new AnyUiLambdaActionNone(); + } + + return new AnyUiLambdaActionNone(); + }); // Cut, copy, paste within list of AASes this.DispPlainIdentifiableCutCopyPasteHelper( @@ -2115,6 +2159,46 @@ public void DisplayOrEditAasEntitySubmodelOrRef( // manually redraw this.appEventsProvider?.PushApplicationEvent(new AasxPluginResultEventRedrawAllElements()); } + }, + extraMenu: new AasxMenu() + .AddAction("delete-sm-in-repo", "Delete SM \u274c in Repo", + "Delete Submodel by Id in a given Repository or Registry."), + lambdaExtraMenuAsync: async (buttonNdx) => + { + if (buttonNdx == 0) + { + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.Submodels, submodel.GetReference()); + + // simply prepare one Key! + var smKey = new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, "" + submodel.Id) }; + + // call function + // (only the side info in the _specific_ endpoint gives information, in which + // repo the CDs could be deleted) + await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( + null, context, + "Delete Submodel in Repository/ Registry", + "Submodel", + smKey, + runtimeOptions: packages.CentralRuntimeOptions, + presetRecord: new PackageContainerHttpRepoSubset.DeleteAssistantJobRecord() + { + // assume Repo ?! + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + + // extract base address + BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo?.Endpoint?.AbsoluteUri)?.AbsoluteUri + }); + + // ok + return new AnyUiLambdaActionNone(); + } + + return new AnyUiLambdaActionNone(); }); } @@ -2133,7 +2217,7 @@ public void DisplayOrEditAasEntitySubmodelOrRef( inputGesture: "Ctrl+Shift+Delete") .AddAction("delete-sm-in-repo", "Delete SM \u274c in Repo", "Delete Submodel by Id in a given Repository or Registry."), - ticketAction: (buttonNdx, ticket) => + ticketActionAsync: async (buttonNdx, ticket) => { if (buttonNdx == 0) if (AnyUiMessageBoxResult.Yes == this.context.MessageBoxFlyoutShow( @@ -2158,6 +2242,39 @@ public void DisplayOrEditAasEntitySubmodelOrRef( return new AnyUiLambdaActionRedrawAllElements(nextFocus: null, isExpanded: null); } + if (buttonNdx == 1) + { + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.Submodels, submodel.GetReference()); + + // simply prepare one Key! + var smKey = new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, "" + submodel.Id) }; + + // call function + // (only the side info in the _specific_ endpoint gives information, in which + // repo the CDs could be deleted) + await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( + ticket, context, + "Delete Submodel in Repository/ Registry", + "Submodel", + smKey, + runtimeOptions: packages.CentralRuntimeOptions, + presetRecord: new PackageContainerHttpRepoSubset.DeleteAssistantJobRecord() + { + // assume Repo ?! + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + + // extract base address + BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo?.Endpoint?.AbsoluteUri)?.AbsoluteUri + }); + + // ok + return new AnyUiLambdaActionNone(); + } + return new AnyUiLambdaActionNone(); }); } @@ -2390,14 +2507,16 @@ public void DisplayOrEditAasEntitySubmodelOrRef( return new AnyUiLambdaActionNone(); var cdIds = lrs.Select((lr) => lr?.Reference?.GetAsExactlyOneKey()?.Value); + var cdKeys = cdIds.Select((cdid) => new Aas.Key(KeyTypes.ConceptDescription, cdid)).Cast(); // call function // (only the side info in the _specific_ endpoint gives information, in which // repo the CDs could be deleted) - await PackageContainerHttpRepoSubset.AssistantDeleteCDsInRepo( + await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( ticket, context, "Delete CDs in Repository/ Registry", - cdIds, + "ConceptDescription", + cdKeys, runtimeOptions: packages.CentralRuntimeOptions, presetRecord: new PackageContainerHttpRepoSubset.DeleteAssistantJobRecord() { @@ -2876,7 +2995,9 @@ public void DisplayOrEditAasEntitySubmodelStub( "Loads the Identifiable data from available data source.") .AddAction("stub-load-all", "Load all", "Loads data from available data source for all " + - "Identifiable stubs in environment."), + "Identifiable stubs in environment.") + .AddAction("delete-sm-in-repo", "Delete Submodel \u274c in Repo", + "Delete Submodel by Id in a given Repository or Registry."), ticketActionAsync: async (buttonNdx, ticket) => { if (buttonNdx == 0) @@ -2897,6 +3018,43 @@ public void DisplayOrEditAasEntitySubmodelStub( } } + if (buttonNdx == 2) + { + // enough info + if (sideInfo.StubLevel < AasIdentifiableSideInfoLevel.IdOnly + || sideInfo.Id?.HasContent() != true) + { + Log.Singleton.Error("No Id information available for deleting Identifiable in " + + "Repository or Registry."); + return new AnyUiLambdaActionNone(); + } + + // simply prepare one Key! + var smKey = new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, "" + sideInfo.Id) }; + + // call function + // (only the side info in the _specific_ endpoint gives information, in which + // repo the CDs could be deleted) + await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( + ticket, context, + "Delete Submodels in Repository/ Registry", + "Submodel", + smKey, + runtimeOptions: packages.CentralRuntimeOptions, + presetRecord: new PackageContainerHttpRepoSubset.DeleteAssistantJobRecord() + { + // assume Repo ?! + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + + // extract base address + BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo?.Endpoint?.AbsoluteUri)?.AbsoluteUri + }); + + // ok + return new AnyUiLambdaActionNone(); + } + return new AnyUiLambdaActionNone(); }); } diff --git a/src/AasxPackageLogic/DispEditHelperMultiElement.cs b/src/AasxPackageLogic/DispEditHelperMultiElement.cs index afbdce59c..8b2a4f4b8 100644 --- a/src/AasxPackageLogic/DispEditHelperMultiElement.cs +++ b/src/AasxPackageLogic/DispEditHelperMultiElement.cs @@ -15,6 +15,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System; using System.Collections.Generic; using System.Linq; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; using static AnyUi.AnyUiDialogueDataTextEditor; using Aas = AasCore.Aas3_0; @@ -471,6 +472,31 @@ public void EntityListSupplementaryFileHelper( }); } + protected void AddToListOfKey( + List list, + Aas.IEnvironment env, + object listOfIdf, + Aas.KeyTypes keyType, + string id, + Aas.IReference reference, + ref AasIdentifiableSideInfo sideInfo) where T : Aas.IIdentifiable + { + // access + if (list == null || listOfIdf == null || id?.HasContent() != true || reference?.IsValid() != true) + return; + + // look for side infos + if (sideInfo == null) + { + var si = OnDemandListIdentifiable.FindSideInfoInListOfIdentifiables(listOfIdf, reference); + if (si != null) + sideInfo = si; + } + + // ordinary add + list.Add(new Aas.Key(keyType, id)); + } + public void DisplayOrEditAasEntityMultipleElements( PackageCentral.PackageCentral packages, ListOfVisualElementBasic entities, @@ -680,6 +706,83 @@ public void DisplayOrEditAasEntityMultipleElements( this.theCopyPaste, entities, superMenu: superMenu); } + // delete Identifiable in repo? + // Note: this is OVERLY COMPLEX, but I did not find any other way :-/// + int wrongElems = 0; + var idfToDel = new List(); + AasIdentifiableSideInfo sideInfo = null; + foreach (var x in entities) + { + if (x is VisualElementAdminShell veaas2 && veaas2?.theAas?.Id?.HasContent() == true) + AddToListOfKey( + idfToDel, veaas2.theEnv, + veaas2.theEnv?.AssetAdministrationShells, KeyTypes.AssetAdministrationShell, + veaas2.theAas.Id, veaas2.theAas.GetReference(), ref sideInfo); + else + if (x is VisualElementSubmodel vesm2 && vesm2?.theSubmodel?.Id?.HasContent() == true) + AddToListOfKey( + idfToDel, vesm2.theEnv, + vesm2.theEnv?.Submodels, KeyTypes.Submodel, + vesm2.theSubmodel.Id, vesm2.theSubmodel.GetReference(), ref sideInfo); + else + if (x is VisualElementSubmodelRef vesmr2 && vesmr2?.theSubmodelRef?.IsValid() == true) + AddToListOfKey( + idfToDel, vesmr2.theEnv, + vesmr2.theEnv?.Submodels, KeyTypes.Submodel, + vesmr2.theSubmodel.Id, vesmr2.theSubmodel.GetReference(), ref sideInfo); + else + if (x is VisualElementSubmodelStub vesmst && vesmst.theSideInfo?.Id?.HasContent() == true) + AddToListOfKey( + idfToDel, vesmst.thePackEnv?.AasEnv, + vesmst.thePackEnv?.AasEnv?.Submodels, KeyTypes.Submodel, + vesmst.theSideInfo.Id, + new Aas.Reference(ReferenceTypes.ModelReference, + (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, vesmst.theSideInfo.Id) }).ToList()), + ref sideInfo); + else + if (x is VisualElementConceptDescription vecd2 && vecd2?.theCD?.Id?.HasContent() == true) + AddToListOfKey( + idfToDel, vecd2.theEnv, + vecd2.theEnv?.ConceptDescriptions, KeyTypes.ConceptDescription, + vecd2.theCD.Id, vecd2.theCD.GetReference(), ref sideInfo); + else wrongElems++; + } + + if (idfToDel.Count() > 0 && wrongElems == 0) + { + AddActionPanel(stack, "Actions:", + repo: repo, superMenu: superMenu, + ticketMenu: new AasxMenu() + .AddAction("delete-idf-in-repo", "Delete Identifiable(s) \u274c in Repo", + "Delete Identifiable(s) by Id in a given Repository or Registry."), + ticketActionAsync: async (buttonNdx, ticket) => + { + if (buttonNdx == 0) + { + // call function + // (only the side info in the _specific_ endpoint gives information, in which + // repo the CDs could be deleted) + await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( + ticket, context, + "Delete Identifiables in Repository/ Registry", + "Identifiables", + idfToDel, + runtimeOptions: packages.CentralRuntimeOptions, + presetRecord: new PackageContainerHttpRepoSubset.DeleteAssistantJobRecord() + { + // assume Repo ?! + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + + // extract base address + BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo?.Endpoint?.AbsoluteUri)?.AbsoluteUri + }); + } + + return new AnyUiLambdaActionNone(); + }); + } + // // Change element attributes? // diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index 211ed480f..d5c4f3ba3 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -108,6 +108,7 @@ public static AasxMenu CreateMainMenu() .AddWpfBlazor(name: "ConnectRest", header: "Connect via REST …", inputGesture: "F6")) .AddSeparator() .AddMenu(header: "API for Registry and Repository …", childs: (new AasxMenu()) + .AddWpf(name: "AddBaseAddress", header: "Add preset for base address …") .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …", args: new AasxMenuListOfArgDefs() .AddFromReflection(new PackageContainerHttpRepoSubset.ConnectExtendedRecord())) diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 979cdbac1..e51cae31a 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -579,6 +579,35 @@ await PackageContainerHttpRepoSubset.PerformUploadAssistant( } } + if (cmd == "addbaseaddress") + { + // start + ticket.StartExec(); + + //do + try + { + // ask for an additional address + var uc = new AnyUiDialogueDataTextBox("Add base address:", + symbol: AnyUiMessageBoxImage.Question); + await DisplayContext.StartFlyoverModalAsync(uc); + if (!uc.Result) + return; + + // add + if (uc.Text.HasContent()) + { + Options.Curr.BaseAddresses.Add(uc.Text); + Log.Singleton.Info("Base address temporarily added to presets: {0}", + uc.Text); + } + } + catch (Exception ex) + { + LogErrorToTicket(ticket, ex, "when performing api upload assistant"); + } + } + if (cmd == "comparesmt") { // start diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index a69665109..0bacc4c32 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -451,6 +451,11 @@ public class OptionsInformation Cmd = "-allow-fake-responses")] public bool AllowFakeResponses = true; + [OptionDescription(Description = + "Presets for base addresses for Registries and Repositories.", + Cmd = "-base-addresses")] + public List BaseAddresses = new List(); + [OptionDescription(Description = "When connecting to Registry/ Repository, add more details to the log messages.", Cmd = "-extended-connection-debug")] diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index 3ec5f9c3e..94f263ffd 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -268,11 +268,19 @@ protected async Task TrySaveAllTaintedIdentifiablesOf( var count = 0; for (int i = 0; i < list.Count(); i++) { +#if __old // Note: elements with side info are not relevant, as only fetched elements // need to be written back .. var si = list.GetSideInfo(i); if (si != null) continue; +#else + // Get the side info. Continue, if side info present (was dynamically fetched) but + // is no stub + var si = list.GetSideInfo(i); + if (si == null || si.IsStub) + continue; +#endif // access var idf = list[i]; @@ -298,7 +306,7 @@ protected async Task TrySaveAllTaintedIdentifiablesOf( reUseClient: null, idf: idf, destUri: uri); - if (res2 == null || res2.Item1 != HttpStatusCode.OK) + if (res2 == null || ((res2.Item1 != HttpStatusCode.OK) && (res2.Item1 != HttpStatusCode.NoContent))) { _runtimeOptions?.Log?.Error("Save of modified Identifiable returned error {0} for id={1} at {2}", "" + ((res2 != null) ? (int)res2.Item1 : -1), diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 8c2c36e2e..b28b86a5a 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -372,7 +372,10 @@ public static Uri BuildUriForRepoSingleSubmodel( // query string for aasId? var queryAasId = ""; if (addAasId && aasId?.HasContent() == true) - queryAasId = "?aasIdentifier=" + aasId; + { + var aasidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(aasId) : aasId; + queryAasId = "?aasIdentifier=" + aasidenc; + } // try combine if (!usePost) @@ -1225,17 +1228,20 @@ await Parallel.ForEachAsync(lrs, prepSM.Add(null, new AasIdentifiableSideInfo() { IsStub = true, - StubLevel = AasIdentifiableSideInfoLevel.IdOnly, - Id = lr.Reference.Keys[0].Value + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = lr.Reference.Keys[0].Value, + IdShort = "", + Endpoint = BuildUriForRepoSingleSubmodel(baseUri, lr.Reference) }); } } else { // no side info => full element + var sourceUri = BuildUriForRepoSingleSubmodel(baseUri, lr.Reference); await PackageHttpDownloadUtil.HttpGetToMemoryStream( null, - sourceUri: BuildUriForRepoSingleSubmodel(baseUri, lr.Reference), + sourceUri: sourceUri, allowFakeResponses: allowFakeResponses, runtimeOptions: runtimeOptions, lambdaDownloadDoneOrFail: (code, ms, contentFn) => @@ -1255,7 +1261,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, Id = sm.Id, IdShort = sm.IdShort, - Endpoint = new Uri(fullItemLocation) + Endpoint = sourceUri }); } } @@ -1717,18 +1723,35 @@ public static async Task PerformConnectExtendedDialogue( items: ConnectExtendedRecord.BaseTypeEnumNames, selectedIndex: (int)record.BaseType, margin: new AnyUiThickness(0, 0, 5, 0), - padding: new AnyUiThickness(0, -1, 0, -3)), + padding: new AnyUiThickness(0, 0, 0, 0)), minWidth: 200, maxWidth: 200), (i) => { record.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); - AnyUiUIElement.SetStringFromControl( + if (displayContext is AnyUiContextPlusDialogs cpd + && cpd.HasCapability(AnyUiContextCapability.WPF)) + { + AnyUiUIElement.SetStringFromControl( helper.Set( - helper.AddSmallTextBoxTo(g2, 0, 1, - text: $"{record.BaseAddress}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { record.BaseAddress = s; }); + helper.AddSmallComboBoxTo(g2, 0, 1, + isEditable: true, + items: Options.Curr.BaseAddresses?.ToArray(), + text: "" + record.BaseAddress, + margin: new AnyUiThickness(0, 0, 0, 0), + padding: new AnyUiThickness(0, 0, 0, 0), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), + (s) => { record.BaseAddress = s; }); + } + else + { + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g2, 0, 1, + text: $"{record.BaseAddress}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.BaseAddress = s; }); + } row++; @@ -2178,14 +2201,31 @@ public static async Task PerformUploadAssistant( minWidth: 200, maxWidth: 200), (i) => { recordJob.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); - AnyUiUIElement.SetStringFromControl( + if (displayContext is AnyUiContextPlusDialogs cpd + && cpd.HasCapability(AnyUiContextCapability.WPF)) + { + AnyUiUIElement.SetStringFromControl( helper.Set( - helper.AddSmallTextBoxTo(g2, 0, 1, - text: $"{recordJob.BaseAddress}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { recordJob.BaseAddress = s; }); + helper.AddSmallComboBoxTo(g2, 0, 1, + isEditable: true, + items: Options.Curr.BaseAddresses?.ToArray(), + text: "" + recordJob.BaseAddress, + margin: new AnyUiThickness(0, 0, 0, 0), + padding: new AnyUiThickness(0, 0, 0, 0), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), + (s) => { recordJob.BaseAddress = s; }); + } + else + { + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g2, 0, 1, + text: $"{recordJob.BaseAddress}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { recordJob.BaseAddress = s; }); + } row++; @@ -2442,7 +2482,8 @@ public static async Task PerformUploadAssistant( if (idf is Aas.IAssetAdministrationShell) location = BuildUriForRepoSingleAAS(baseUri, idf?.Id, encryptIds: true, usePost: usePost); if (idf is Aas.ISubmodel) - location = BuildUriForRepoSingleSubmodel(baseUri, idf?.Id, encryptIds: true, usePost: usePost); + location = BuildUriForRepoSingleSubmodel(baseUri, idf?.Id, encryptIds: true, usePost: usePost, + addAasId: true, aasId: aasId); if (idf is Aas.IConceptDescription) location = BuildUriForRepoSingleCD(baseUri, idf?.Id, encryptIds: true, usePost: usePost); if (location == null) @@ -2535,30 +2576,29 @@ await Parallel.ForEachAsync(rowsToUpload, public class DeleteAssistantJobRecord { - // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; public string BaseAddress = ""; - // public string BaseAddress = "https://eis-data.aas-voyager.com/"; - // public string BaseAddress = "http://smt-repo.admin-shell-io.com/api/v3.0"; - // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; - public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository; - // public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Registry; } - public static async Task AssistantDeleteCDsInRepo( + /// + /// Deletes a range of Identifiables from an Repo / Registry. + /// + /// Each key to be an individual Identifiable! + public static async Task AssistantDeleteIdfsInRepo( AasxMenuActionTicket ticket, AnyUiContextBase displayContext, string caption, - IEnumerable cdIds, + string elemKindName, + IEnumerable idfIds, PackCntRuntimeOptions runtimeOptions = null, PackageContainerListBase containerList = null, DeleteAssistantJobRecord presetRecord = null) { // access - if (displayContext == null || caption?.HasContent() != true || cdIds == null || cdIds.Count() < 1) + if (displayContext == null || caption?.HasContent() != true || idfIds == null || idfIds.Count() < 1) return false; - var cdIdsDis = cdIds.Distinct().ToList(); + var idfIdsDis = idfIds.Distinct().ToList(); // // Screen 1 : ask for job / Repo @@ -2586,7 +2626,8 @@ public static async Task AssistantDeleteCDsInRepo( // Statistics helper.AddSmallLabelTo(g, row, 0, content: "Requested to delete:"); - helper.AddSmallLabelTo(g, row + 0, 1, content: "# of ConceptDescription: " + cdIds.Count()); + helper.AddSmallLabelTo(g, row + 0, 1, + content: $"# of {elemKindName}: " + idfIds.Count()); row += 1; @@ -2614,14 +2655,31 @@ public static async Task AssistantDeleteCDsInRepo( minWidth: 200, maxWidth: 200), (i) => { recordJob.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); - AnyUiUIElement.SetStringFromControl( + if (displayContext is AnyUiContextPlusDialogs cpd + && cpd.HasCapability(AnyUiContextCapability.WPF)) + { + AnyUiUIElement.SetStringFromControl( helper.Set( - helper.AddSmallTextBoxTo(g2, 0, 1, - text: $"{recordJob.BaseAddress}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { recordJob.BaseAddress = s; }); + helper.AddSmallComboBoxTo(g2, 0, 1, + isEditable: true, + items: Options.Curr.BaseAddresses?.ToArray(), + text: "" + recordJob.BaseAddress, + margin: new AnyUiThickness(0, 0, 0, 0), + padding: new AnyUiThickness(0, 0, 0, 0), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), + (s) => { recordJob.BaseAddress = s; }); + } + else + { + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g2, 0, 1, + text: $"{recordJob.BaseAddress}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { recordJob.BaseAddress = s; }); + } row++; @@ -2637,13 +2695,14 @@ public static async Task AssistantDeleteCDsInRepo( // var ucProgTest = new AnyUiDialogueDataProgress( - "Checking individual ConceptDescription to exist", + $"Checking individual {elemKindName}s to exist", info: "Preparing ...", symbol: AnyUiMessageBoxImage.Information); ucProgTest.Progress = 0.0; - var numTotal = cdIdsDis.Count; - var cdExist = new List(); + var numTotal = idfIdsDis.Count; + var idfExist = new List(); var numOK = 0; + var numWrongType = 0; var numNOK = 0; // in order to re-use sockets @@ -2663,39 +2722,66 @@ public static async Task AssistantDeleteCDsInRepo( if (recordJob.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) { // first, test which ids exist as CD in repo - await PackageHttpDownloadUtil.DownloadListOfIdentifiables( + await PackageHttpDownloadUtil.DownloadListOfIdentifiables( client, - cdIdsDis, - lambdaGetLocation: (id) => + idfIdsDis, + lambdaGetLocation: (key) => { - // return new Uri("https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NSMjU1MTYzUjExNjUvYWFz"); - return BuildUriForRepoSingleCD(baseUri, id, encryptIds: true); + Uri location = null; + if (key?.Type == KeyTypes.AssetAdministrationShell) + location = BuildUriForRepoSingleAAS(baseUri, key.Value, encryptIds: true); + if (key?.Type == KeyTypes.Submodel) + location = BuildUriForRepoSingleSubmodel(baseUri, key.Value, encryptIds: true); + if (key?.Type == KeyTypes.ConceptDescription) + location = BuildUriForRepoSingleCD(baseUri, key.Value, encryptIds: true); + return location; + }, + lambdaGetTypeToSerialize: (key) => + { + Type type = null; + if (key?.Type == KeyTypes.AssetAdministrationShell) + type = typeof(Aas.IAssetAdministrationShell); + if (key?.Type == KeyTypes.Submodel) + type = typeof(Aas.ISubmodel); + if (key?.Type == KeyTypes.ConceptDescription) + type = typeof(Aas.IConceptDescription); + return type; }, runtimeOptions: runtimeOptions, useParallel: Options.Curr.MaxParallelOps > 1, - lambdaDownloadDoneOrFail: (code, idf, contentFn, id) => + lambdaDownloadDoneOrFail: (code, idf, contentFn, key) => { // need mutex - lock (cdExist) + lock (idfExist) { // stat Action lambdaStat = (ok) => { if (ok) { numOK++; } else { numNOK++; }; - ucProgTest.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + - $"Id: {idf?.Id}"; + ucProgTest.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + + $"{numWrongType} were of wrong type,\n" + + $"Id: {idf?.Id}"; ucProgTest.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); }; + // match the types + var matchType = (idf != null && key != null) && ( + (idf is Aas.IAssetAdministrationShell && key.Type == KeyTypes.AssetAdministrationShell) + || (idf is Aas.ISubmodel && key.Type == KeyTypes.Submodel) + || (idf is Aas.IConceptDescription && key.Type == KeyTypes.ConceptDescription) + ); + if (idf != null && !matchType) + numWrongType++; + // any error? - if (code != HttpStatusCode.OK || idf == null) + if (code != HttpStatusCode.OK || idf == null || !matchType) { lambdaStat(false); return; } // remember for later - cdExist.Add(idf); + idfExist.Add(idf); lambdaStat(true); } }); @@ -2710,19 +2796,19 @@ public static async Task AssistantDeleteCDsInRepo( await displayContext.StartFlyoverModalAsync(ucProgTest); // test - if (cdExist.Count < 1) + if (idfExist.Count < 1) { runtimeOptions?.Log.Info(StoredPrint.Color.Blue, - "No ConceptDescriptions to delete found. Finalizing! Location: {0}", + $"No {elemKindName} to delete found. Finalizing! Location: {0}", recordJob.BaseAddress); return false; } // ask to proceed? if (AnyUiMessageBoxResult.Yes != displayContext.MessageBoxFlyoutShow( - $"After checking individual ids, {cdExist.Count} ConceptDescriptions seem to " + + $"After checking individual ids, {idfExist.Count} {elemKindName}s seem to " + $"exist on the server. Proceed with deleting these?", - "Delete ConceptDescriptions", + $"Delete {elemKindName}", AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) { runtimeOptions?.Log.Info("Aborted."); @@ -2734,11 +2820,11 @@ public static async Task AssistantDeleteCDsInRepo( // var ucProgDel = new AnyUiDialogueDataProgress( - "Deleting individual ConceptDescription to exist", + $"Deleting individual {elemKindName} to exist", info: "Preparing ...", symbol: AnyUiMessageBoxImage.Information); ucProgTest.Progress = 0.0; - numTotal = cdExist.Count; + numTotal = idfExist.Count; numOK = 0; numNOK = 0; @@ -2752,27 +2838,33 @@ public static async Task AssistantDeleteCDsInRepo( var baseUri = new Uri(recordJob.BaseAddress); // second, send deletes - await PackageHttpDownloadUtil.DeleteListOfEntities( + await PackageHttpDownloadUtil.DeleteListOfEntities( client, - cdExist, - lambdaGetLocation: (cd) => + idfExist, + lambdaGetLocation: (idf) => { - // return new Uri("https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NSMjU1MTYzUjExNjUvYWFz"); - return BuildUriForRepoSingleCD(baseUri, cd.Id, encryptIds: true); + Uri location = null; + if (idf is Aas.IAssetAdministrationShell) + location = BuildUriForRepoSingleAAS(baseUri, idf?.Id, encryptIds: true); + if (idf is Aas.ISubmodel) + location = BuildUriForRepoSingleSubmodel(baseUri, idf?.Id, encryptIds: true); + if (idf is Aas.IConceptDescription) + location = BuildUriForRepoSingleCD(baseUri, idf?.Id, encryptIds: true); + return location; }, runtimeOptions: runtimeOptions, useParallel: Options.Curr.MaxParallelOps > 1, - lambdaDeleteDoneOrFail: (code, content, cd2) => + lambdaDeleteDoneOrFail: (code, content, idf) => { // need mutex - lock (cdExist) + lock (idfExist) { // stat Action lambdaStat = (ok) => { if (ok) { numOK++; } else { numNOK++; }; ucProgDel.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + - $"Id: {cd2?.Id}"; + $"Id: {idf?.Id}"; ucProgDel.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); }; @@ -2799,8 +2891,8 @@ public static async Task AssistantDeleteCDsInRepo( // okay? runtimeOptions?.Log.Info(StoredPrint.Color.Blue, - "{0} ConceptDescriptions deleted successfull, {1} NOK. Location: {2}", - numOK, numNOK, recordJob.BaseAddress); + "{0} {1}s deleted successfully, {2} NOK. Location: {3}", + numOK, elemKindName, numNOK, recordJob.BaseAddress); return true; } diff --git a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs index 1faae8d9f..f9fa05cca 100644 --- a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs +++ b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs @@ -37,7 +37,7 @@ public class ImageMapAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private ImageMapOptions _options = null; private PluginEventStack _eventStack = null; @@ -79,7 +79,7 @@ public ImageMapAnyUiControl() public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, ImageMapOptions theOptions, PluginEventStack eventStack, @@ -110,7 +110,7 @@ public static ImageMapAnyUiControl FillWithAnyUiControls( AasxPluginBase plugin) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -134,7 +134,7 @@ public static ImageMapAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // test trivial access @@ -154,7 +154,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, IEnumerable foundRecs, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // make an outer grid, very simple grid of two rows: header & body From 1ac55c5cca4ec06a123efdb1902ec541d58a7619 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sat, 2 Nov 2024 16:18:06 +0100 Subject: [PATCH 24/99] * refactor File to Blob ask to convert file contents, as well * plugin for ImageMap shows Blobs as well --- src/AasxCsharpLibrary/AdminShellUtil.cs | 35 ++++++++++ .../Extensions/ExtendBlob.cs | 2 + .../AnyUI/AnyUiMagickHelper.cs | 25 +++++++ src/AasxPackageExplorer/MainWindow.xaml.cs | 69 +++++++++++++------ src/AasxPackageExplorer/debug.MIHO.script | 2 +- .../options-debug.MIHO.json | 3 +- src/AasxPackageLogic/DispEditHelperBasics.cs | 52 ++++++++++++-- .../DispEditHelperEntities.cs | 12 ++-- .../DispEditHelperMiniModules.cs | 2 +- .../ImageMapAnyUiControl.cs | 23 +++++-- 10 files changed, 185 insertions(+), 40 deletions(-) diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index dc5435c55..a5d68cd69 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -267,6 +267,7 @@ public static string[] GetPopularMimeTypes() }; } + public static bool CheckForTextContentType(string input) { if (input == null) @@ -285,6 +286,40 @@ public static bool CheckForTextContentType(string input) return true; return false; } + + public static string GuessExtension(string contentType = null, byte[] contents = null) + { + if (contentType?.HasContent() == true) + { + var list = GetPopularMimeTypes().ToList(); + var p = list.IndexOf(contentType); + if (p >= 0) + return (new[] { + ".txt", + ".xml", + ".html", + ".md", + ".adoc", + ".json", + ".rdf", + ".pdf", + ".jpg", + ".png", + ".gif", + ".iges", + ".stp" + })[p]; + } + + // ok, guess by bytes + if (contents != null && contents.Length > 0) + { + return GuessImageTypeExtension(contents); + } + + // ok, nop + return ".tmp"; + } public static IEnumerable GetAdequateEnums(AasSubmodelElements[] excludeValues = null, AasSubmodelElements[] includeValues = null) { diff --git a/src/AasxCsharpLibrary/Extensions/ExtendBlob.cs b/src/AasxCsharpLibrary/Extensions/ExtendBlob.cs index d8ee065e8..0beb6c8d7 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendBlob.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendBlob.cs @@ -66,6 +66,8 @@ public static Blob UpdateFrom(this Blob elem, ISubmodelElement source) { if (srcFile.Value != null) elem.Value = Encoding.Default.GetBytes(srcFile.Value); + if (srcFile.ContentType != null) + elem.ContentType = srcFile.ContentType; } return elem; diff --git a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs index 93307ae9a..5756445f2 100644 --- a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs +++ b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs @@ -83,6 +83,31 @@ public static AnyUiBitmapInfo CreateAnyUiBitmapFromResource(string path, return null; } + public static AnyUiBitmapInfo LoadBitmapInfoFromBytes(byte[] ba) + { + if (ba == null || ba.Length < 1) + return null; + + try + { + using (var ms = new MemoryStream(ba)) + { + // load image + var bi = new MagickImage(ms); + var binfo = CreateAnyUiBitmapInfo(bi); + + // give this back + return binfo; + } + } + catch (Exception ex) + { + AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); + } + + return null; + } + public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageEnvBase package, string path) { if (package == null || path == null) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 42e86f16e..69a49f670 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -85,7 +85,7 @@ public PackageCentral PackageCentral public AasxMenuWpf MainMenu = new AasxMenuWpf(); - private Aas.ISubmodelElement showContentElement = null; + private Aas.ISubmodelElement _showContentElement = null; private VisualElementGeneric currentEntityForUpdate = null; private IFlyoutControl currentFlyoutControl = null; @@ -666,7 +666,7 @@ public void PrepareDispEditEntity( ShowContent.IsEnabled = false; DragSource.Foreground = Brushes.DarkGray; UpdateContent.IsEnabled = false; - this.showContentElement = null; + _showContentElement = null; // show it if (ElementTabControl.SelectedIndex != 0) @@ -678,15 +678,14 @@ public void PrepareDispEditEntity( if (sme?.theWrapper is Aas.IFile file) { ShowContent.IsEnabled = true; - this.showContentElement = file; + _showContentElement = file; DragSource.Foreground = Brushes.Black; } - if (sme?.theWrapper is Aas.IBlob blb - && AdminShellUtil.CheckForTextContentType(blb.ContentType)) + if (sme?.theWrapper is Aas.IBlob blb) { ShowContent.IsEnabled = true; - this.showContentElement = blb; + this._showContentElement = blb; DragSource.Foreground = Brushes.Black; } } @@ -3082,35 +3081,63 @@ private void Window_SizeChanged(object sender, SizeChangedEventArgs e) private void ShowContent_Click(object sender, RoutedEventArgs e) { - if (sender == ShowContent && this.showContentElement != null && PackageCentral.MainAvailable) + if (sender == ShowContent && _showContentElement != null && PackageCentral.MainAvailable) { - if (this.showContentElement is Aas.IFile scFile) + Tuple contentFound = null; + if (_showContentElement is Aas.IFile scFile) + contentFound = new Tuple(scFile.Value, scFile.ContentType); + if (_showContentElement is Aas.IBlob scBlob && MainMenu?.IsChecked("EditMenu") == false) + contentFound = new Tuple(scBlob.Value, scBlob.ContentType); + + if (contentFound != null) { - Log.Singleton.Info("Trying display content {0} ..", scFile.Value); + Log.Singleton.Info("Trying display content {0} ..", contentFound.Item1); try { - var contentUri = scFile.Value; + if (contentFound.Item1 is string contentUri) + { + // if local in the package, then make a tempfile + if (!contentUri.ToLower().Trim().StartsWith("http://") + && !contentUri.ToLower().Trim().StartsWith("https://")) + { + // make it as file + contentUri = PackageCentral.Main.MakePackageFileAvailableAsTempFile(contentUri); + } - // if local in the package, then make a tempfile - if (!contentUri.ToLower().Trim().StartsWith("http://") - && !contentUri.ToLower().Trim().StartsWith("https://")) + BrowserDisplayLocalFile(contentUri, contentFound.Item2); + } + else + if (contentFound.Item1 is byte[] ba) { - // make it as file - contentUri = PackageCentral.Main.MakePackageFileAvailableAsTempFile(contentUri); + try + { + // generate tempfile name + string tempext = AdminShellUtil.GuessExtension( + contentType: contentFound.Item2, + contents: ba); + string temppath = System.IO.Path.GetTempFileName().Replace(".tmp", tempext); + + // write it + System.IO.File.WriteAllBytes(temppath, ba); + + // display + BrowserDisplayLocalFile(temppath, contentFound.Item2); + } catch (Exception ex) + { + Log.Singleton.Error(ex, "when preparing BLOB contents to be displayed as file."); + } } - - BrowserDisplayLocalFile(contentUri, scFile.ContentType); } catch (Exception ex) { Log.Singleton.Error( - ex, $"When displaying content {scFile.Value}, an error occurred"); + ex, $"When displaying content {contentFound.Item1}, an error occurred"); return; } - Log.Singleton.Info("Content {0} displayed.", scFile.Value); + Log.Singleton.Info("Content {0} displayed.", contentFound.Item1); } - if (this.showContentElement is Aas.IBlob blb + if (this._showContentElement is Aas.IBlob blb && MainMenu?.IsChecked("EditMenu") == true && AdminShellUtil.CheckForTextContentType(blb.ContentType)) { @@ -3648,7 +3675,7 @@ private void DragSource_PreviewMouseMove(object sender, MouseEventArgs e) //// && (Math.Abs(dragStartPoint.X) < 0.001 && Math.Abs(dragStartPoint.Y) < 0.001) if (e.LeftButton == MouseButtonState.Pressed && !isDragging && PackageCentral.MainAvailable - && this.showContentElement is Aas.IFile scFile) + && this._showContentElement is Aas.IFile scFile) { Point position = e.GetPosition(null); if (Math.Abs(position.X - dragStartPoint.X) > SystemParameters.MinimumHorizontalDragDistance || diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index c3bf35e81..77230c5af 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,7 +9,7 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); +// Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); // Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index b999cc3f7..de44041e1 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -52,7 +52,8 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\MTP\\test-data-SMT_MTP.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\00_FestoDemoBox-Module-2-Kopie.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work_spiel.aasx", - "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", + "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\a33.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v02.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SAMSON_TROVIS_3730-3_Vorlage_Demo_Achema.aasx", diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index b10cd585c..16b626ee1 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -1114,7 +1114,7 @@ public bool SmartSelectEclassEntity( /// /// Asks the user for SME element type, allowing exclusion of types. /// - public Aas.AasSubmodelElements SelectAdequateEnum( + public async Task SelectAdequateEnum( string caption, Aas.AasSubmodelElements[] excludeValues = null, Aas.AasSubmodelElements[] includeValues = null, AasxMenuActionTicket ticket = null) @@ -1135,7 +1135,7 @@ public Aas.AasSubmodelElements SelectAdequateEnum( var uc = new AnyUiDialogueDataSelectFromList( caption: caption); uc.ListOfItems = fol; - this.context.StartFlyoverModal(uc); + await this.context.StartFlyoverModalAsync(uc); if (uc.Result && uc.ResultItem != null && uc.ResultItem.Tag != null && uc.ResultItem.Tag is Aas.AasSubmodelElements) { @@ -1147,17 +1147,54 @@ public Aas.AasSubmodelElements SelectAdequateEnum( return Aas.AasSubmodelElements.SubmodelElement; } + public async Task SmartRefactorSme_PostProcess( + AdminShellPackageEnvBase packEnv, + Aas.ISubmodelElement oldSme, + Aas.ISubmodelElement newSme) + { + if (newSme is Aas.IBlob newBlob && oldSme is Aas.IFile oldFile) + { + // get file contents from a file + var ba = packEnv?.GetByteArrayFromUriOrLocalPackage(oldFile.Value); + if (ba == null || ba.Length < 1) + return false; + + // ask back + if (AnyUiMessageBoxResult.Yes != await this.context.MessageBoxFlyoutShowAsync( + $"Local file contents with a len of {ba.Length} bytes found. " + + $"Convert these to BLOB contents? " + + $"Note: This will significantly increase the size of serialized Submodel " + + $"in total", + "Convert local file to BLOB contents?", AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Question)) + return false; + + // work & tell + newBlob.Value = ba; + Log.Singleton.Info(StoredPrint.Color.Blue, + "When refactoring File to Blob, the following file was converted to BLOB contents of {0} bytes: {1}", + ba.Length, oldFile.Value); + + // ok! + return true; + } + + // nope + return false; + } + /// /// Asks the user, to which SME to refactor to, create the new SME and returns it. /// - public Aas.ISubmodelElement SmartRefactorSme(Aas.ISubmodelElement oldSme) + public async Task SmartRefactorSme( + AdminShellPackageEnvBase packEnv, + Aas.ISubmodelElement oldSme) { // access if (oldSme == null) return null; // ask - var en = SelectAdequateEnum( + var en = await SelectAdequateEnum( $"Refactor {oldSme.GetSelfDescription().AasElementName} '{"" + oldSme.IdShort}' to new element type ..", excludeValues: new[] { Aas.AasSubmodelElements.DataElement, @@ -1166,7 +1203,7 @@ public Aas.ISubmodelElement SmartRefactorSme(Aas.ISubmodelElement oldSme) if (en == Aas.AasSubmodelElements.SubmodelElement) return null; - if (AnyUiMessageBoxResult.Yes == this.context.MessageBoxFlyoutShow( + if (AnyUiMessageBoxResult.Yes == await this.context.MessageBoxFlyoutShowAsync( "Recfactor selected entity? " + "This operation will change the selected submodel element and " + "delete specific attributes. It can not be reverted!", @@ -1178,6 +1215,11 @@ public Aas.ISubmodelElement SmartRefactorSme(Aas.ISubmodelElement oldSme) // which? var refactorSme = AdminShellUtil.CreateSubmodelElementFromEnum(en, oldSme, defaultHelper: Options.Curr.GetCreateDefaultHelper()); + + // post work? + await SmartRefactorSme_PostProcess(packEnv, oldSme, refactorSme); + + // ok return refactorSme; } } diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 3c28bd48c..9c52528e8 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -3463,7 +3463,7 @@ public void DisplayOrEditAasEntityOperationVariable( "Adds a selected kind of SubmodelElement to the containing collection.", args: new AasxMenuListOfArgDefs() .Add("Kind", "Name (not abbreviated) of kind of SubmodelElement.")), - ticketAction: (buttonNdx, ticket) => + ticketActionAsync: async (buttonNdx, ticket) => { if (buttonNdx >= 0 && buttonNdx <= 3) { @@ -3476,7 +3476,7 @@ public void DisplayOrEditAasEntityOperationVariable( if (buttonNdx == 2) en = Aas.AasSubmodelElements.SubmodelElementCollection; if (buttonNdx == 3) - en = this.SelectAdequateEnum( + en = await this.SelectAdequateEnum( "Select SubmodelElement to create ..", excludeValues: new[] { Aas.AasSubmodelElements.DataElement, @@ -3698,12 +3698,12 @@ public void DisplayOrEditAasEntitySubmodelElement( ticketMenu: new AasxMenu() .AddAction("refactor", "Refactor", "Takes the selected AAS element and converts it to a new kind, keeping most of the attributes."), - ticketAction: (buttonNdx, ticket) => + ticketActionAsync: async (buttonNdx, ticket) => { if (buttonNdx == 0) { // which? - var refactorSme = this.SmartRefactorSme(sme); + var refactorSme = await this.SmartRefactorSme(packages.Main, sme); var parMgr = (parentContainer as Aas.IReferable); // ok? @@ -4108,7 +4108,7 @@ public void DisplayOrEditAasEntitySubmodelElement( "Adds a selected kind of SubmodelElement to the containing collection.", args: new AasxMenuListOfArgDefs() .Add("Kind", "Name (not abbreviated) of kind of SubmodelElement.")), - ticketAction: (buttonNdx, ticket) => + ticketActionAsync: async (buttonNdx, ticket) => { if (buttonNdx >= 0 && buttonNdx <= 3) { @@ -4121,7 +4121,7 @@ public void DisplayOrEditAasEntitySubmodelElement( if (buttonNdx == 2) en = Aas.AasSubmodelElements.SubmodelElementCollection; if (buttonNdx == 3) - en = this.SelectAdequateEnum( + en = await this.SelectAdequateEnum( "Select SubmodelElement to create ..", excludeValues: new[] { Aas.AasSubmodelElements.DataElement, diff --git a/src/AasxPackageLogic/DispEditHelperMiniModules.cs b/src/AasxPackageLogic/DispEditHelperMiniModules.cs index 67c9f400c..61d504fb9 100644 --- a/src/AasxPackageLogic/DispEditHelperMiniModules.cs +++ b/src/AasxPackageLogic/DispEditHelperMiniModules.cs @@ -1928,7 +1928,7 @@ public void DispSmeListAddNewHelper( Aas.AasSubmodelElements.Blob, Aas.AasSubmodelElements.ReferenceElement}; - en = this.SelectAdequateEnum("Select SubmodelElement to create ..", ticket: ticket, + en = await this.SelectAdequateEnum("Select SubmodelElement to create ..", ticket: ticket, includeValues: includes, excludeValues: new[] { Aas.AasSubmodelElements.DataElement, diff --git a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs index f9fa05cca..63a739470 100644 --- a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs +++ b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs @@ -373,15 +373,28 @@ protected void SetBasicInfos() return; // image + AnyUiBitmapInfo bi = null; + // file? - var fe = _submodel.SubmodelElements.FindFirstSemanticIdAs( + var fe = _submodel.SubmodelElements.FindFirstSemanticIdAs( AasxPredefinedConcepts.ImageMap.Static.CD_ImageFile, MatchMode.Relaxed); - if (fe?.Value == null) - return; + if (fe?.Value != null) + { + bi = AnyUiGdiHelper.LoadBitmapInfoFromPackage(_package, fe.Value); + } + + // BLOB + var be = _submodel.SubmodelElements.FindFirstSemanticIdAs( + AasxPredefinedConcepts.ImageMap.Static.CD_ImageFile, + MatchMode.Relaxed); + if (be?.Value != null) + { + bi = AnyUiGdiHelper.LoadBitmapInfoFromBytes(be.Value); + } - var bi = AnyUiGdiHelper.LoadBitmapInfoFromPackage(_package, fe.Value); - if (_backgroundImage != null) + // set + if (bi != null && _backgroundImage != null) { if (bi != null) { From 3ef55d18309793cd0417302f8606b20e9625d6fa Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 3 Nov 2024 11:38:02 +0100 Subject: [PATCH 25/99] * 1st implementation of centralize files .. still ugly --- .../AdminShellPackageFileBasedEnv.cs | 328 +++++++++++++----- src/AasxCsharpLibrary/AdminShellUtil.cs | 42 ++- .../DispEditHelperEntities.cs | 74 +--- src/AasxPackageLogic/DispEditHelperModules.cs | 132 ++++++- 4 files changed, 408 insertions(+), 168 deletions(-) diff --git a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs index feb8981bd..d38cdeeda 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs @@ -17,6 +17,8 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection.Metadata; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; @@ -287,11 +289,134 @@ public virtual string Filename } } + protected static WebProxy proxy = null; + public virtual Stream GetLocalStreamFromPackage( string uriString, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read) { + // + // this part of the functionality works on HTTP and absolute files and is + // indepedent from a package storage. + // + + // split scheme and part + var sap = AdminShellUtil.GetSchemeAndPath(uriString); + if (sap == null) + return null; + + // Check, if remote + if (sap.Scheme.StartsWith("http")) + { + if (proxy == null) + { + string proxyAddress = ""; + string username = ""; + string password = ""; + + string proxyFile = "proxy.txt"; + if (System.IO.File.Exists(proxyFile)) + { + try + { // Open the text file using a stream reader. + using (StreamReader sr = new StreamReader(proxyFile)) + { + proxyFile = sr.ReadLine(); + } + } + catch (IOException e) + { + Console.WriteLine("proxy.txt could not be read:"); + Console.WriteLine(e.Message); + } + } + + try + { + using (StreamReader sr = new StreamReader(proxyFile)) + { + proxyAddress = sr.ReadLine(); + username = sr.ReadLine(); + password = sr.ReadLine(); + } + } + catch (Exception e) + { + Console.WriteLine(e.Message); + Console.WriteLine(proxyFile + " not found!"); + } + + if (proxyAddress != "") + { + proxy = new WebProxy(); + Uri newUri = new Uri(proxyAddress); + proxy.Address = newUri; + proxy.Credentials = new NetworkCredential(username, password); + Console.WriteLine("Using proxy: " + proxyAddress); + } + } + + var handler = new HttpClientHandler(); + + if (proxy != null) + handler.Proxy = proxy; + else + handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; + var hc = new HttpClient(handler); + + var response = hc.GetAsync(uriString).GetAwaiter().GetResult(); + + // if you call response.EnsureSuccessStatusCode here it will throw an exception + if (response.StatusCode == HttpStatusCode.Moved + || response.StatusCode == HttpStatusCode.Found) + { + var location = response.Headers.Location; + response = hc.GetAsync(location).GetAwaiter().GetResult(); + } + + response.EnsureSuccessStatusCode(); + var s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + + if (s.Length < 500) // indirect load? + { + StreamReader reader = new StreamReader(s); + string json = reader.ReadToEnd(); + var parsed = JObject.Parse(json); + try + { + string url = parsed.SelectToken("url").Value(); + response = hc.GetAsync(url).GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + } + + return s; + } + + // now, has to be file + if (sap.Scheme != "file") + return null; + + // if not starting with "/", if has to be an absolute file + if (!sap.Path.StartsWith('/')) + { + try + { + var stream = System.IO.File.Open(sap.Path, mode, access); + return stream; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + } + return null; } @@ -416,6 +541,104 @@ public virtual void Flush() public virtual void Dispose() { } + + // + // Binary file read + write + // + + public async Task GetByteArrayFromExternalInternalUri(string uri) + { + // split uri and access + var sap = AdminShellUtil.GetSchemeAndPath(uri); + if (sap == null) + return null; + + // directly a HTTP resource? + if (sap.Scheme.StartsWith("http")) + { + try + { + using (var client = new HttpClient()) + { + var ba = await client.GetByteArrayAsync(uri); + return ba; + } + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + return null; + } + } + + // no other schemes now + if (sap.Scheme != "file") + return null; + + // check if package local file + if (IsLocalFile(sap.Path)) + { + return GetByteArrayFromUriOrLocalPackage(sap.Path); + } + + // OK, assume a file accessible to this computer + try + { + var ba = await System.IO.File.ReadAllBytesAsync(sap.Path); + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + + // nope + return null; + } + + public async Task PutByteArrayToExternalUri(string uri, byte[] ba) + { + // split uri and access + var sap = AdminShellUtil.GetSchemeAndPath(uri); + if (ba == null || sap == null) + return false; + + // directly a HTTP resource? + if (sap.Scheme.StartsWith("http")) + { + try + { + using (var client = new HttpClient()) + using (var content = new ByteArrayContent(ba)) + { + content.Headers.ContentType = new MediaTypeHeaderValue("*/*"); + var response = await client.PostAsync(uri, content); + return response.IsSuccessStatusCode; + } + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + return false; + } + } + + // no other schemes now + if (sap.Scheme != "file") + return false; + + // just write + try + { + System.IO.File.WriteAllBytes(sap.Path, ba); + return true; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + + return false; + } } /// @@ -1474,108 +1697,25 @@ public override bool IsLocalFile(string uriString) return isLocal; } - private static WebProxy proxy = null; - public override Stream GetLocalStreamFromPackage(string uriString, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read) { - // Check, if remote - if (uriString.ToLower().Substring(0, 4) == "http") - { - if (proxy == null) - { - string proxyAddress = ""; - string username = ""; - string password = ""; - - string proxyFile = "proxy.txt"; - if (System.IO.File.Exists(proxyFile)) - { - try - { // Open the text file using a stream reader. - using (StreamReader sr = new StreamReader(proxyFile)) - { - proxyFile = sr.ReadLine(); - } - } - catch (IOException e) - { - Console.WriteLine("proxy.txt could not be read:"); - Console.WriteLine(e.Message); - } - } + // IMPORTANT! First try to use the base implementation to get an stream to + // HTTP or ABSOLUTE file + var absStream = base.GetLocalStreamFromPackage(uriString, mode, access); + if (absStream != null) + return absStream; - try - { - using (StreamReader sr = new StreamReader(proxyFile)) - { - proxyAddress = sr.ReadLine(); - username = sr.ReadLine(); - password = sr.ReadLine(); - } - } - catch (Exception e) - { - Console.WriteLine(e.Message); - Console.WriteLine(proxyFile + " not found!"); - } - - if (proxyAddress != "") - { - proxy = new WebProxy(); - Uri newUri = new Uri(proxyAddress); - proxy.Address = newUri; - proxy.Credentials = new NetworkCredential(username, password); - Console.WriteLine("Using proxy: " + proxyAddress); - } - } - - var handler = new HttpClientHandler(); - - if (proxy != null) - handler.Proxy = proxy; - else - handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; - var hc = new HttpClient(handler); - - var response = hc.GetAsync(uriString).GetAwaiter().GetResult(); - - // if you call response.EnsureSuccessStatusCode here it will throw an exception - if (response.StatusCode == HttpStatusCode.Moved - || response.StatusCode == HttpStatusCode.Found) - { - var location = response.Headers.Location; - response = hc.GetAsync(location).GetAwaiter().GetResult(); - } - - response.EnsureSuccessStatusCode(); - var s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); - - if (s.Length < 500) // indirect load? - { - StreamReader reader = new StreamReader(s); - string json = reader.ReadToEnd(); - var parsed = JObject.Parse(json); - try - { - string url = parsed.SelectToken("url").Value(); - response = hc.GetAsync(url).GetAwaiter().GetResult(); - response.EnsureSuccessStatusCode(); - s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); - } - catch - { - } - } - - return s; - } + // now, split uri string (again) for ourselves + var sap = AdminShellUtil.GetSchemeAndPath(uriString); + if (sap == null) + return null; - // access + // now, it has to be an package file if (_openPackage == null) throw (new Exception(string.Format($"AASX Package {_fn} not opened. Aborting!"))); // exist - var puri = new Uri(uriString, UriKind.RelativeOrAbsolute); + var puri = new Uri(sap.Path, UriKind.RelativeOrAbsolute); if (!_openPackage.PartExists(puri)) throw (new Exception(string.Format($"AASX Package has no part {uriString}. Aborting!"))); diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index a5d68cd69..cbca0ab3c 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -528,9 +528,10 @@ public static string ToSingleLineShortened(string str, int maxLen, string textNe /// Assert.AreEqual("someName", AdminShellUtil.FilterFriendlyName("someName")); /// Assert.AreEqual("some__name", AdminShellUtil.FilterFriendlyName("some!;name")); /// - public static string FilterFriendlyName(string src, + public static string FilterFriendlyName(string src, bool pascalCase = false, - bool fixMoreBlanks = false) + bool fixMoreBlanks = false, + string regexForFilter = null) { if (src == null) return null; @@ -538,7 +539,8 @@ public static string FilterFriendlyName(string src, if (pascalCase && src.Length > 0) src = char.ToUpper(src[0]) + src.Substring(1); - src = Regex.Replace(src, @"[^a-zA-Z0-9\-_]", "_"); + var regex = regexForFilter ?? @"[^a-zA-Z0-9\-_]"; + src = Regex.Replace(src, regex, "_"); if (fixMoreBlanks) { @@ -1228,6 +1230,38 @@ public static string GetExtensionWoQuery(string fn) return ext; } + public class SchemeAndPath + { + public string Scheme = ""; + public string Path = ""; + } + + /// + /// Split info. Limits to 5 character schemes! + /// + /// null for any error! + public static SchemeAndPath GetSchemeAndPath(string uri) + { + // error? + if (uri?.HasContent() != true) + return null; + + // search :// + var p = uri.IndexOf("://"); + if (p > 5) + return null; + + // nothing? + if (p < 0) + return new SchemeAndPath() { Scheme = "file", Path = uri }; + + // split + return new SchemeAndPath() { + Scheme = uri.Substring(0, p).ToLower(), + Path = uri.Substring(p+3) + }; + } + // // Base 64 // @@ -1474,5 +1508,7 @@ public static string GetDefaultLngIso639() { return DefaultLngIso639; } + + } } diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 9c52528e8..05eefe3fd 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -409,7 +409,9 @@ public void DisplayOrEditAasEntityAssetInformation( // editMode: editMode, repo: repo, stack: substack, hintMode: hintMode); // dead-csharp on DisplayOrEditEntityFileResource( - substack, aas, repo, superMenu, + substack, + packages.Main, + aas, repo, superMenu, asset.DefaultThumbnail.Path, asset.DefaultThumbnail.ContentType, (fn, ct) => { @@ -4742,7 +4744,8 @@ public void DisplayOrEditAasEntitySubmodelElement( // refer to mini-module DisplayOrEditEntityFileResource( - stack, fl, repo, superMenu, + stack, packages.Main, + fl, repo, superMenu, fl.Value, fl.ContentType, (fn, ct) => { @@ -4796,37 +4799,6 @@ public void DisplayOrEditAasEntitySubmodelElement( } return new AnyUiLambdaActionNone(); }); - - //Note: migrated in order to show fields via "real" value hash - //AddKeyValueExRef( - // stack, "value", blb, (blb.Value == null) ? "" : Encoding.Default.GetString(blb.Value), - // null, repo, - // v => - // { - // blb.Value = Encoding.Default.GetBytes((string)v); - // this.AddDiaryEntry(blb, new DiaryEntryUpdateValue()); - // return new AnyUiLambdaActionNone(); - // }, - // limitToOneRowForNoEdit: true, - // auxButtonTitles: new[] { "\u2261" }, - // auxButtonToolTips: new[] { "Edit in multiline editor" }, - // auxButtonLambda: (buttonNdx) => - // { - // if (buttonNdx == 0) - // { - // var uc = new AnyUiDialogueDataTextEditor( - // caption: $"Edit Blob '{"" + blb.IdShort}'", - // mimeType: blb.ContentType, - // text: Encoding.Default.GetString(blb.Value ?? new byte[0])); - // if (this.context.StartFlyoverModal(uc)) - // { - // blb.Value = Encoding.Default.GetBytes(uc.Text); - // this.AddDiaryEntry(blb, new DiaryEntryUpdateValue()); - // return new AnyUiLambdaActionRedrawEntity(); - // } - // } - // return new AnyUiLambdaActionNone(); - // }); } else { @@ -5373,44 +5345,8 @@ public void DisplayOrEditAasEntitySubmodelElement( this.AddDiaryEntry(ent, new DiaryEntryStructChange()); return new AnyUiLambdaActionNone(); }); - //AddKeyReference( - // stack, "globalAssetId", ent.GlobalAssetId, repo, packages, - // PackageCentral.PackageCentral.Selector.MainAuxFileRepo, - // addExistingEntities: "All", showRefSemId: false, - // jumpLambda: lambda, - // noEditJumpLambda: lambda, - // relatedReferable: ent, - // auxContextHeader: new[] { "\u2573", "Delete globalAssetId" }, - // auxContextLambda: (i) => - // { - // if (i == 0) - // { - // ent.GlobalAssetId = null; - // this.AddDiaryEntry(ent, new DiaryEntryStructChange()); - // return new AnyUiLambdaActionRedrawEntity(); - // } - // return new AnyUiLambdaActionNone(); - // }); } - // in V3.0RC01 this was falsely [0..1] - //this.DisplayOrEditEntitySingleIdentifierKeyValuePair( - // stack, ent.SpecificAssetIds, - // (v) => { ent.SpecificAssetIds = v; }, - // key: "specificAssetId", - // relatedReferable: ent, - // auxContextHeader: new[] { "\u2573", "Delete SpecificAssetId" }, - // auxContextLambda: (o) => - // { - // if (o is int i && i == 0) - // { - // ent.SpecificAssetIds = null; - // this.AddDiaryEntry(ent, new DiaryEntryStructChange()); - // return new AnyUiLambdaActionRedrawEntity(); - // } - // return new AnyUiLambdaActionNone(); - // }); - // dead-csharp on this.DisplayOrEditEntityListOfSpecificAssetIds(stack, ent.SpecificAssetIds, (ico) => { ent.SpecificAssetIds = ico; }, key: "specificAssetId", diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 5b0a27dce..3eaa44ef4 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -34,6 +34,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Runtime.Serialization; using System.Text; using AasxPackageLogic.PackageCentral; +using System.Threading.Tasks; namespace AasxPackageLogic { @@ -2393,7 +2394,111 @@ public void DisplayOrEditEntitySubmodelRef(AnyUiStackPanel stack, // File / Resource attributes // + public async Task CentralizeFile( + AdminShellPackageEnvBase packEnv, + string centralStorePath, + string filePath, + bool askForTargetPath = false, + bool? deleteSourcePath = false, + Action lambdaSetFilePath = null) + { + // access + if (packEnv == null || centralStorePath?.HasContent() != true || filePath?.HasContent() != true) + return false; + + // try accessing it + var ba = await packEnv.GetByteArrayFromExternalInternalUri(filePath); + if (ba == null || ba.Length < 1) + { + Log.Singleton.Error("Centralize file: cannot read file: {0}", filePath); + return false; + } + + // filePath shall only contain harmless chars and NO separators, but dots! + var filterFilePath = AdminShellUtil.FilterFriendlyName(filePath, + regexForFilter: @"[^a-zA-Z0-9\-_.]", + fixMoreBlanks: true); + + // add a UUID in front + var newFilePath = Guid.NewGuid() + "_" + filterFilePath; + + // build target path + if (centralStorePath.EndsWith('/') || centralStorePath.EndsWith('\\')) + centralStorePath = centralStorePath.Substring(0, centralStorePath.Length - 1); + var targetPath = Path.Combine(centralStorePath, newFilePath); + + // ask for target path? + if (askForTargetPath) + { + var uc = new AnyUiDialogueDataTextBox( + $"Centralize file with {ba.Length} bytes", + text: targetPath, + symbol: AnyUiMessageBoxImage.Question, + maxWidth: 1400); + await context?.StartFlyoverModalAsync(uc); + if (!uc.Result) + return false; + + targetPath = uc.Text; + if (targetPath?.HasContent() != true) + return false; + } + + // try to write there? + // assuming file storage writable to computer + var res = await packEnv.PutByteArrayToExternalUri(targetPath, ba); + if (!res) + { + Log.Singleton.Error("Centralize file: error writing bytes to location: {0}", targetPath); + return false; + } + + // can set new filename + lambdaSetFilePath?.Invoke(targetPath); + + // delete only possible for local files + if (packEnv.IsLocalFile(filePath)) + { + if (deleteSourcePath == null) + { + // ask if to delete + deleteSourcePath = AnyUiMessageBoxResult.Yes == await context.MessageBoxFlyoutShowAsync( + "Delete selected entity? This operation can not be reverted!", + "Centralize file", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Question); + } + + // delete? + if (deleteSourcePath.Value) + { + var psfs = packEnv.GetListOfSupplementaryFiles(); + var psf = psfs?.FindByUri(filePath); + if (psf == null) + { + Log.Singleton.Error("Centralize file: unable to find existing file in package " + + "before deleting: {0}", targetPath); + return false; + } + + packEnv.DeleteSupplementaryFile(psf); + + Log.Singleton.Info(StoredPrint.Color.Blue, + "Centralized file {0} bytes to new location and deleted original: {1}. " + + "A save operation is required for the package!", + ba.Length, newFilePath); + } + } + + // do the normal info + Log.Singleton.Info(StoredPrint.Color.Blue, + "Centralized file {0} bytes to new location and deleted original: {1}.", + ba.Length, targetPath); + + return true; + } + public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, + AdminShellPackageEnvBase packEnv, Aas.IReferable containingObject, ModifyRepo repo, AasxMenu superMenu, string valuePath, @@ -2500,8 +2605,10 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, .AddAction("create-text", "Create text file", "Creates a text file and adds it to the AAS environment.") .AddAction("edit-text", "Edit text file", - "Edits the associated text file and updates it to the AAS environment."), - ticketAction: (buttonNdx, ticket) => + "Edits the associated text file and updates it to the AAS environment.") + .AddAction("centralize-file", "Centralize file", + "Rename file, copy it to central file storage and potentially delete supplemental file."), + ticketActionAsync: async (buttonNdx, ticket) => { if (buttonNdx == 0 && valuePath.HasContent()) @@ -2686,6 +2793,27 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, return new AnyUiLambdaActionRedrawEntity(); } + if (buttonNdx == 3 && valuePath.HasContent()) + { + var changed = false; + await CentralizeFile( + packEnv, + "file://c:\\HOMI\\Develop\\Aasx\\central-store", + valuePath, + askForTargetPath: true, + deleteSourcePath: null, + lambdaSetFilePath: (v) => + { + changed = true; + valuePath = v; + setOutput?.Invoke(valuePath, valueContent); + this.AddDiaryEntry(containingObject, new DiaryEntryStructChange()); + }); + + if (changed) + return new AnyUiLambdaActionRedrawAllElements(nextFocus: relatedReferable); + } + return new AnyUiLambdaActionNone(); }); From 315957de3ac292e1ba44b652ba0de5f01d0f0ec3 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 3 Nov 2024 12:36:27 +0100 Subject: [PATCH 26/99] * dreams came true: found "PUT attachment" as operation in swagger! --- .../options-debug.MIHO.json | 4 + src/AasxPackageLogic/DispEditHelperModules.cs | 251 ++++++++++++++---- src/AasxPackageLogic/Options.cs | 5 + 3 files changed, 204 insertions(+), 56 deletions(-) diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index de44041e1..36277e0de 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -113,6 +113,10 @@ "http://localhost:5001/", "http://localhost:5001/api/v3.0/" ], + "CentralStores": [ + "file://C:\\AasxStore" /* mount point for file shares, WebDAV etc */ + "https://store.example.com/client123" /* example, not implemented */ + ], "ExtendedConnectionDebug": false, "AllowFakeResponses": true, "RestServerHost": "localhost", diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 3eaa44ef4..c437dac9d 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -35,6 +35,8 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Text; using AasxPackageLogic.PackageCentral; using System.Threading.Tasks; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; +using System.Security.Cryptography; namespace AasxPackageLogic { @@ -2394,16 +2396,168 @@ public void DisplayOrEditEntitySubmodelRef(AnyUiStackPanel stack, // File / Resource attributes // - public async Task CentralizeFile( +#if __not_required_obsolete_by_PUT_attachment_operation + public class CentralizeFilesRecord + { + public string CentralStoreUri = ""; + public string UUID = ""; + public bool DeleteFileAfter = false; + } + + /// + /// Implements a crude, very short version of UUID, that is, base64 over hash over GUID. + /// For alternatives, see: https://stackoverflow.com/questions/9278909/net-short-unique-identifier + /// + public string GetShortUUid() + { + var uuid = Guid.NewGuid().ToString(); + var hash = uuid.GetHashCode().ToString("X8"); + var base64 = AdminShellUtil.Base64UrlEncode(hash); + return base64; + } + + public CentralizeFilesRecord GenerateNewCentralizeFilesRecord() + { + return new CentralizeFilesRecord() + { + CentralStoreUri = "" + Options.Curr.CentralStores?.FirstOrDefault(), + UUID = GetShortUUid(), + DeleteFileAfter = false + }; + } + + public static async Task PerformCentralizeFilesDialogue( + AnyUiContextBase displayContext, + string caption, + CentralizeFilesRecord record, + string info = null) + { + // access + if (displayContext == null || caption?.HasContent() != true || record == null) + return false; + + // ok, go on .. + var uc = new AnyUiDialogueDataModalPanel(caption); + uc.ActivateRenderPanel(record, + disableScrollArea: false, + dialogButtons: AnyUiMessageBoxButton.OK, + renderPanel: (uci) => + { + // create panel + var panel = new AnyUiStackPanel(); + var helper = new AnyUiSmallWidgetToolkit(); + + var g = helper.AddSmallGrid(25, 2, new[] { "180:", "*" }, + padding: new AnyUiThickness(0, 5, 0, 5), + margin: new AnyUiThickness(10, 0, 30, 0)); + + panel.Add(g); + + // dynamic rows + int row = 0; + + // info? + if (info?.HasContent() == true) + { + // Statistics + helper.Set( + helper.AddSmallLabelTo(g, row, 0, content: info), + colSpan: 2); + row++; + + // separation + helper.AddSmallBorderTo(g, row, 0, + borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White, + colSpan: 2, + margin: new AnyUiThickness(0, 0, 0, 20)); + row++; + } + + // Central store URI + helper.AddSmallLabelTo(g, row, 0, content: "Central store URI:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + if (displayContext is AnyUiContextPlusDialogs cpd + && cpd.HasCapability(AnyUiContextCapability.WPF)) + { + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g, row, 1, + isEditable: true, + items: Options.Curr.CentralStores?.ToArray(), + text: "" + record.CentralStoreUri, + margin: new AnyUiThickness(0, 0, 0, 0), + padding: new AnyUiThickness(0, 0, 0, 0), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), + (s) => { record.CentralStoreUri = s; }); + } + else + { + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row, 1, + text: $"{record.CentralStoreUri}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.CentralStoreUri = s; }); + } + + row++; + + // UUID + helper.AddSmallLabelTo(g, row, 0, content: "UUID (header):", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row, 1, + text: $"{record.UUID}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.UUID = s; }); + + row++; + + // Delete + helper.AddSmallLabelTo(g, row, 0, content: "Post process:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Delete supplementary file, if local", + isChecked: record.DeleteFileAfter, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.DeleteFileAfter = b; }); + row++; + + // give back + return g; + }); + + if (!(await displayContext.StartFlyoverModalAsync(uc))) + return false; + + // ok + return true; + } + + public async Task PerformCentralizeFileExecution( AdminShellPackageEnvBase packEnv, - string centralStorePath, + CentralizeFilesRecord record, string filePath, - bool askForTargetPath = false, - bool? deleteSourcePath = false, Action lambdaSetFilePath = null) { // access - if (packEnv == null || centralStorePath?.HasContent() != true || filePath?.HasContent() != true) + if (packEnv == null + || record?.CentralStoreUri?.HasContent() != true + || record?.UUID?.HasContent() != true + || filePath?.HasContent() != true) return false; // try accessing it @@ -2419,30 +2573,14 @@ public async Task CentralizeFile( regexForFilter: @"[^a-zA-Z0-9\-_.]", fixMoreBlanks: true); - // add a UUID in front - var newFilePath = Guid.NewGuid() + "_" + filterFilePath; + // add the UUID in front + var newFilePath = record.UUID.Trim() + "_" + filterFilePath; // build target path + var centralStorePath = record.CentralStoreUri; if (centralStorePath.EndsWith('/') || centralStorePath.EndsWith('\\')) centralStorePath = centralStorePath.Substring(0, centralStorePath.Length - 1); - var targetPath = Path.Combine(centralStorePath, newFilePath); - - // ask for target path? - if (askForTargetPath) - { - var uc = new AnyUiDialogueDataTextBox( - $"Centralize file with {ba.Length} bytes", - text: targetPath, - symbol: AnyUiMessageBoxImage.Question, - maxWidth: 1400); - await context?.StartFlyoverModalAsync(uc); - if (!uc.Result) - return false; - - targetPath = uc.Text; - if (targetPath?.HasContent() != true) - return false; - } + var targetPath = Path.Combine(centralStorePath, newFilePath); // try to write there? // assuming file storage writable to computer @@ -2457,45 +2595,35 @@ public async Task CentralizeFile( lambdaSetFilePath?.Invoke(targetPath); // delete only possible for local files - if (packEnv.IsLocalFile(filePath)) + if (packEnv.IsLocalFile(filePath) && record.DeleteFileAfter) { - if (deleteSourcePath == null) + var psfs = packEnv.GetListOfSupplementaryFiles(); + var psf = psfs?.FindByUri(filePath); + if (psf == null) { - // ask if to delete - deleteSourcePath = AnyUiMessageBoxResult.Yes == await context.MessageBoxFlyoutShowAsync( - "Delete selected entity? This operation can not be reverted!", - "Centralize file", - AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Question); + Log.Singleton.Error("Centralize file: unable to find existing file in package " + + "before deleting: {0}", targetPath); + return false; } - // delete? - if (deleteSourcePath.Value) - { - var psfs = packEnv.GetListOfSupplementaryFiles(); - var psf = psfs?.FindByUri(filePath); - if (psf == null) - { - Log.Singleton.Error("Centralize file: unable to find existing file in package " + - "before deleting: {0}", targetPath); - return false; - } + packEnv.DeleteSupplementaryFile(psf); - packEnv.DeleteSupplementaryFile(psf); + Log.Singleton.Info(StoredPrint.Color.Blue, + "Centralized file {0} bytes to new location and deleted original: {1}. " + + "A save operation is required for the package!", + ba.Length, newFilePath); - Log.Singleton.Info(StoredPrint.Color.Blue, - "Centralized file {0} bytes to new location and deleted original: {1}. " + - "A save operation is required for the package!", - ba.Length, newFilePath); - } + return true; } // do the normal info Log.Singleton.Info(StoredPrint.Color.Blue, - "Centralized file {0} bytes to new location and deleted original: {1}.", + "Centralized file {0} bytes to new location (preserved original): {1}.", ba.Length, targetPath); return true; } +#endif public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, AdminShellPackageEnvBase packEnv, @@ -2606,8 +2734,11 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, "Creates a text file and adds it to the AAS environment.") .AddAction("edit-text", "Edit text file", "Edits the associated text file and updates it to the AAS environment.") +#if __not_required_obsolete_by_PUT_attachment_operation .AddAction("centralize-file", "Centralize file", - "Rename file, copy it to central file storage and potentially delete supplemental file."), + "Rename file, copy it to central file storage and potentially delete supplemental file.") +#endif + , ticketActionAsync: async (buttonNdx, ticket) => { @@ -2793,15 +2924,22 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, return new AnyUiLambdaActionRedrawEntity(); } +#if __not_required_obsolete_by_PUT_attachment_operation if (buttonNdx == 3 && valuePath.HasContent()) { var changed = false; - await CentralizeFile( - packEnv, - "file://c:\\HOMI\\Develop\\Aasx\\central-store", + var record = GenerateNewCentralizeFilesRecord(); + + if (!await PerformCentralizeFilesDialogue( + context, + "Centralize file", + record, + $"File: {valuePath}")) + return new AnyUiLambdaActionNone(); + + await PerformCentralizeFileExecution( + packEnv, record, valuePath, - askForTargetPath: true, - deleteSourcePath: null, lambdaSetFilePath: (v) => { changed = true; @@ -2813,6 +2951,7 @@ await CentralizeFile( if (changed) return new AnyUiLambdaActionRedrawAllElements(nextFocus: relatedReferable); } +#endif return new AnyUiLambdaActionNone(); }); diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index 0bacc4c32..f06af2956 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -456,6 +456,11 @@ public class OptionsInformation Cmd = "-base-addresses")] public List BaseAddresses = new List(); + [OptionDescription(Description = + "Central store to hold supplementary files for File elements.", + Cmd = "-central-stores")] + public List CentralStores = new List(); + [OptionDescription(Description = "When connecting to Registry/ Repository, add more details to the log messages.", Cmd = "-extended-connection-debug")] From 5613c75724250c70e478cee85779651058a356a6 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 3 Nov 2024 22:31:36 +0100 Subject: [PATCH 27/99] * update some of the plugins * use of base env everywhere --- src/AasxBammRdfImExport/RDFimport.cs | 2 +- .../AdminShellPackageFileBasedEnv.cs | 27 ++- .../Extensions/ExtendIReferable.cs | 12 +- .../Extensions/ExtendISubmodelElement.cs | 2 +- src/AasxFormatCst/AasxToCst.cs | 4 +- .../AasForms/AasFormUtils.cs | 2 +- .../AasForms/FormInstance.cs | 26 +-- .../AnyUI/AnyUiMagickHelper.cs | 130 ++++++++++- .../AnyUI/AnyUiTimerHelper.cs | 13 ++ .../AasxWpfBaseUtils.cs | 2 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 2 +- src/AasxPackageExplorer/debug.MIHO.script | 2 +- .../options-debug.MIHO.json | 6 +- src/AasxPackageLogic/IMainWindow.cs | 2 +- .../MainWindowAnyUiDialogs.cs | 2 +- .../AdminShellPackageDynamicFetchEnv.cs | 55 ++++- .../PackageCentral/PackageCentral.cs | 2 +- .../PackageContainerHttpRepoSubset.cs | 204 ++++++++++++++++++ .../PackageContainerListBase.cs | 4 +- .../PackageCentral/PackageHttpDownloadUtil.cs | 26 +++ .../AssetInterfaceAnyUiControl.cs | 12 +- .../AasReferenceStore.cs | 3 + .../GenericBomControl.cs | 8 +- .../ContactEntity.cs | 2 +- .../ContactListAnyUiControl.cs | 6 +- src/AasxPluginDocumentShelf/DocumentEntity.cs | 6 +- .../ShelfAnyUiControl.cs | 6 +- .../ShelfPreviewService.cs | 4 +- .../GenericFormsAnyUiControl.cs | 6 +- .../ImageMapAnyUiControl.cs | 19 +- .../KnownSubmodelAnyUiControl.cs | 12 +- .../WpfMtpControlWrapper.xaml.cs | 9 +- src/AasxPluginPlotting/PlotItem.cs | 2 +- .../PlottingViewControl.xaml.cs | 4 +- src/AasxPluginPlotting/Plugin.cs | 2 +- .../PcnAnyUiControl.cs | 16 +- .../TechnicalDataAnyUiControl.cs | 136 +++++++----- .../ConceptModelZveiTechnicalData.cs | 4 +- src/BlazorExplorer/Data/AASService.cs | 6 +- .../Data/BlazorSession.MainWindow.cs | 2 +- src/BlazorExplorer/Data/BlazorSession.cs | 2 +- 41 files changed, 647 insertions(+), 145 deletions(-) diff --git a/src/AasxBammRdfImExport/RDFimport.cs b/src/AasxBammRdfImExport/RDFimport.cs index 40b5cb69a..191cf6a74 100644 --- a/src/AasxBammRdfImExport/RDFimport.cs +++ b/src/AasxBammRdfImExport/RDFimport.cs @@ -24,7 +24,7 @@ namespace AasxBammRdfImExport public static class BAMMRDFimport { - public static AdminShellNS.AdminShellPackageFileBasedEnv thePackageEnv; + public static AdminShellNS.AdminShellPackageEnvBase thePackageEnv; public static void ImportInto( string rdffn, Aas.IEnvironment env, Aas.ISubmodel sm, diff --git a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs index d38cdeeda..79dc2252f 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs @@ -293,6 +293,9 @@ public virtual string Filename public virtual Stream GetLocalStreamFromPackage( string uriString, + string aasId = null, + string smId = null, + string idShortPath = null, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read) { @@ -420,6 +423,18 @@ public virtual Stream GetLocalStreamFromPackage( return null; } + public virtual async Task GetLocalStreamFromPackageAsync( + string uriString, + string aasId = null, + string smId = null, + string idShortPath = null, + FileMode mode = FileMode.Open, + FileAccess access = FileAccess.Read) + { + await Task.Yield(); + return GetLocalStreamFromPackage(uriString, aasId, smId, idShortPath, mode, access); + } + public virtual void PrepareSupplementaryFileParameters(ref string targetDir, ref string targetFn) { } @@ -1658,7 +1673,7 @@ public override Stream GetStreamFromUriOrLocalPackage(string uriString, { // local if (IsLocalFile(uriString)) - return GetLocalStreamFromPackage(uriString, mode, access); + return GetLocalStreamFromPackage(uriString, mode: mode, access: access); // no .. return System.IO.File.Open(uriString, mode, access); @@ -1697,11 +1712,17 @@ public override bool IsLocalFile(string uriString) return isLocal; } - public override Stream GetLocalStreamFromPackage(string uriString, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read) + public override Stream GetLocalStreamFromPackage( + string uriString, + string aasId = null, + string smId = null, + string idShortPath = null, + FileMode mode = FileMode.Open, + FileAccess access = FileAccess.Read) { // IMPORTANT! First try to use the base implementation to get an stream to // HTTP or ABSOLUTE file - var absStream = base.GetLocalStreamFromPackage(uriString, mode, access); + var absStream = base.GetLocalStreamFromPackage(uriString, mode: mode, access: access); if (absStream != null) return absStream; diff --git a/src/AasxCsharpLibrary/Extensions/ExtendIReferable.cs b/src/AasxCsharpLibrary/Extensions/ExtendIReferable.cs index e09ce03a0..726f63559 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendIReferable.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendIReferable.cs @@ -465,13 +465,19 @@ public static Submodel GetParentSubmodel(this IReferable referable) return parent as Submodel; } - public static string CollectIdShortByParent(this IReferable referable) + public static string CollectIdShortByParent( + this IReferable referable, + char separatorChar = '/', + bool excludeIdentifiable = false) { // recurse first var head = ""; - if (referable is not IIdentifiable && referable.Parent is IReferable parentReferable) + if (referable is not IIdentifiable + && referable.Parent is IReferable parentReferable + && (!excludeIdentifiable || parentReferable is not IIdentifiable)) // can go up - head = parentReferable.CollectIdShortByParent() + "/"; + head = parentReferable.CollectIdShortByParent(separatorChar, excludeIdentifiable) + + separatorChar; // add own var myid = ""; if (!string.IsNullOrEmpty(referable.IdShort)) diff --git a/src/AasxCsharpLibrary/Extensions/ExtendISubmodelElement.cs b/src/AasxCsharpLibrary/Extensions/ExtendISubmodelElement.cs index 30693a8a9..f625e4716 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendISubmodelElement.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendISubmodelElement.cs @@ -1043,7 +1043,7 @@ public static IEnumerable FindAllSemanticIdAs(this List where T : ISubmodelElement { if (submodelELements.IsNullOrEmpty()) - yield return default(T); + yield break; foreach (var submodelElement in submodelELements) if (submodelElement != null && submodelElement is T && submodelElement.SemanticId != null) diff --git a/src/AasxFormatCst/AasxToCst.cs b/src/AasxFormatCst/AasxToCst.cs index c1eeea564..dc046d06c 100644 --- a/src/AasxFormatCst/AasxToCst.cs +++ b/src/AasxFormatCst/AasxToCst.cs @@ -21,7 +21,7 @@ namespace AasxFormatCst { public class AasxToCst { - protected AdminShellPackageFileBasedEnv _env; + protected AdminShellPackageEnvBase _env; protected int _customIndex = 1; public string CustomNS = "UNSPEC"; @@ -267,7 +267,7 @@ private void RecurseOnSme( } public void ExportSingleSubmodel( - AdminShellPackageFileBasedEnv env, string path, + AdminShellPackageEnvBase env, string path, Aas.Key smId, IEnumerable cdReferables, CstIdObjectBase firstNodeId, diff --git a/src/AasxIntegrationBase/AasForms/AasFormUtils.cs b/src/AasxIntegrationBase/AasForms/AasFormUtils.cs index 1346019e3..873b8d690 100644 --- a/src/AasxIntegrationBase/AasForms/AasFormUtils.cs +++ b/src/AasxIntegrationBase/AasForms/AasFormUtils.cs @@ -213,7 +213,7 @@ private static void RecurseExportAsTemplate( } } - public static void ExportAsTemplate(AdminShellPackageFileBasedEnv package, string fn) + public static void ExportAsTemplate(AdminShellPackageEnvBase package, string fn) { // access if (fn == null || package == null || package.AasEnv == null) diff --git a/src/AasxIntegrationBase/AasForms/FormInstance.cs b/src/AasxIntegrationBase/AasForms/FormInstance.cs index b3520a200..7b144b208 100644 --- a/src/AasxIntegrationBase/AasForms/FormInstance.cs +++ b/src/AasxIntegrationBase/AasForms/FormInstance.cs @@ -320,7 +320,7 @@ public FormDescInstancesPair FindInstance(FormInstanceListOfSame searchInst) /// public List AddOrUpdateDifferentElementsToCollection( List elements, - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false) { // will be a list of newly added elements (for tracing) var res = new List(); @@ -480,7 +480,7 @@ public void PresetInstancesBasedOnSource(List sourceElemen /// Render the form description and adds or updates its instances into a list of SubmodelElements. /// public List AddOrUpdateSameElementsToCollection( - List elements, AdminShellPackageFileBasedEnv packageEnv = null, + List elements, AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false) { // access @@ -1057,7 +1057,7 @@ public void PresetInstancesBasedOnSource(List sourceElemen /// public List AddOrUpdateDifferentElementsToCollection( List elements, - AdminShellPackageFileBasedEnv packageEnv = null, + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { @@ -1165,7 +1165,7 @@ protected void InitReferable(FormDescSubmodelElement desc, Aas.ISubmodelElement /// /// True, if a new element shall be rendered from the instance sme. public virtual bool ProcessSmeForRender( - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { if (this.sme != null && Touched && this.sourceSme != null && editSource) { @@ -1183,7 +1183,7 @@ public virtual bool ProcessSmeForRender( /// public virtual List AddOrUpdateSmeToCollection( List collectionNewElements, - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false) { // typically, there will be only one SME var res = new List(); @@ -1280,7 +1280,7 @@ public FormInstanceListOfDifferent GetListOfDifferent() /// public override List AddOrUpdateSmeToCollection( - List elements, AdminShellPackageFileBasedEnv packageEnv = null, + List elements, AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false) { // SMEC as Refrable @@ -1312,7 +1312,7 @@ public void PresetInstancesBasedOnSource(List sourceElemen /// Render the list of form elements into a list of SubmodelElements. /// public List AddOrUpdateDifferentElementsToCollection( - List elements, AdminShellPackageFileBasedEnv packageEnv = null, + List elements, AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false) { if (this.PairInstances != null) @@ -1422,7 +1422,7 @@ public FormInstanceProperty( /// producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -1616,7 +1616,7 @@ public FormInstanceMultiLangProp( /// the new values instead of producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -1784,7 +1784,7 @@ public FormInstanceFile( /// producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -1969,7 +1969,7 @@ public FormInstanceReferenceElement( /// the new values instead of producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -2108,7 +2108,7 @@ public FormInstanceRelationshipElement( /// the new values instead of producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); @@ -2270,7 +2270,7 @@ public FormInstanceCapability( /// the new values instead of producing a new element. Returns True, if a new element shall be rendered. /// public override bool ProcessSmeForRender( - AdminShellPackageFileBasedEnv packageEnv = null, bool addFilesToPackage = false, bool editSource = false) + AdminShellPackageEnvBase packageEnv = null, bool addFilesToPackage = false, bool editSource = false) { // refer to base (SME) function, but not caring about result base.ProcessSmeForRender(packageEnv, addFilesToPackage, editSource); diff --git a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs index 5756445f2..3af02018f 100644 --- a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs +++ b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs @@ -20,8 +20,14 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Xml.Schema; using System.Xml; using AdminShellNS; +using Aas = AasCore.Aas3_0; +using Extensions; using AnyUi; using ImageMagick; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.IO.Packaging; namespace AasxIntegrationBaseGdi { @@ -135,10 +141,32 @@ public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageEnvBase return null; } + public static AnyUiBitmapInfo LoadBitmapInfoFromStream(Stream stream) + { + if (stream == null) + return null; + + try + { + // load image + var bi = new MagickImage(stream); + var binfo = CreateAnyUiBitmapInfo(bi); + + // give this back + return binfo; + } + catch (Exception ex) + { + AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); + } + + return null; + } + // TODO (MIHO, 2023-02-23): make the whole thing async!! public static AnyUiBitmapInfo MakePreviewFromPackageOrUrl( - AdminShellPackageFileBasedEnv package, string path, + AdminShellPackageEnvBase package, string path, double dpi = 75) { if (path == null) @@ -208,6 +236,106 @@ public static AnyUiBitmapInfo MakePreviewFromPackageOrUrl( return res; } + + // + // Support for loading multiple files one after each other + // + + public class DelayedFileContentLoadBase + { + public Action LambdaLoaded; + } + + public class DelayedFileContentForFileElement : DelayedFileContentLoadBase + { + public AdminShellPackageEnvBase Package; + public string FileUri; + + public string AasId; + public string SmId; + public string IdShortPath; + } + + public class DelayedFileContentLoader + { + protected List _jobs = new List(); + + public void Add(DelayedFileContentLoadBase job) + { + lock (_jobs) + { + _jobs.Add(job); + } + } + + /// + /// Make sure, that Submodel.SetParents() has been executed! + /// + /// + /// + /// + public void Add( + AdminShellPackageEnvBase package, + Aas.IAssetAdministrationShell aas, + Aas.ISubmodel submodel, + Aas.IFile fileElem, + Action lambdaLoaded) + { + // access + if (package == null || aas == null || submodel == null || fileElem == null) + return; + + var idShortPath = "" + fileElem.CollectIdShortByParent( + separatorChar: '.', excludeIdentifiable: true); + + + Add(new DelayedFileContentForFileElement() + { + LambdaLoaded = lambdaLoaded, + Package = package, + AasId = "" + aas.Id, + SmId = "" + submodel.Id, + IdShortPath = idShortPath, + FileUri = fileElem.Value + }); + } + + public async Task TickToLoad() + { + if (_jobs.Count < 1) + return false; + + // pick one and start + DelayedFileContentLoadBase job = null; + lock (_jobs) + { + job = _jobs.First(); + _jobs.RemoveAt(0); + } + if (job == null) + return false; + + // now try loading + if (job is DelayedFileContentForFileElement jobfc) + { + var stream = await jobfc.Package?.GetLocalStreamFromPackageAsync( + uriString: jobfc.FileUri, + aasId: "" + jobfc.AasId, + smId: "" + jobfc.SmId, + idShortPath: jobfc.IdShortPath); + + var bi = AnyUiGdiHelper.LoadBitmapInfoFromStream(stream); + + if (bi != null) + { + jobfc.LambdaLoaded?.Invoke(jobfc, bi); + return true; + } + } + + return false; + } + } } } diff --git a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiTimerHelper.cs b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiTimerHelper.cs index 824ef0b7e..4a10a0b39 100644 --- a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiTimerHelper.cs +++ b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiTimerHelper.cs @@ -25,5 +25,18 @@ public static object CreatePluginTimer(int intervalMs, EventHandler eventHandler return _timer2; } } + + public static object CreatePluginTimerAsync(int intervalMs, Action lambdaTick) + { + { + // Note: this timer shall work for all sorts of applications? + // see: https://stackoverflow.com/questions/21041299/c-sharp-dispatchertimer-in-dll-application-never-triggered + var _timer2 = new System.Timers.Timer(intervalMs); + _timer2.Elapsed += (s, e) => lambdaTick?.Invoke(); + _timer2.Enabled = true; + _timer2.Start(); + return _timer2; + } + } } } diff --git a/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs b/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs index cb8e0b520..5695e2c1f 100644 --- a/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs +++ b/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs @@ -137,7 +137,7 @@ public static IEnumerable LogicalTreeFindAllChildsWithRegexTag } } - public static BitmapImage LoadBitmapImageFromPackage(AdminShellPackageFileBasedEnv package, string path) + public static BitmapImage LoadBitmapImageFromPackage(AdminShellPackageEnvBase package, string path) { if (package == null || path == null) return null; diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 69a49f670..b2d56b7fa 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -379,7 +379,7 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() /// Set the edit mode AFTER loading public void UiLoadPackageWithNew( PackageCentralItem packItem, - AdminShellPackageFileBasedEnv takeOverEnv = null, + AdminShellPackageEnvBase takeOverEnv = null, string loadLocalFilename = null, string info = null, bool onlyAuxiliary = false, diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 77230c5af..c3bf35e81 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,7 +9,7 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -// Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); +Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); // Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 36277e0de..0efb64761 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -52,8 +52,8 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\MTP\\test-data-SMT_MTP.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\00_FestoDemoBox-Module-2-Kopie.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work_spiel.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", - "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\a33.aasx", + "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\a33.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v02.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SAMSON_TROVIS_3730-3_Vorlage_Demo_Achema.aasx", @@ -114,7 +114,7 @@ "http://localhost:5001/api/v3.0/" ], "CentralStores": [ - "file://C:\\AasxStore" /* mount point for file shares, WebDAV etc */ + "file://C:\\AasxStore", /* mount point for file shares, WebDAV etc */ "https://store.example.com/client123" /* example, not implemented */ ], "ExtendedConnectionDebug": false, diff --git a/src/AasxPackageLogic/IMainWindow.cs b/src/AasxPackageLogic/IMainWindow.cs index 59f239343..86031766e 100644 --- a/src/AasxPackageLogic/IMainWindow.cs +++ b/src/AasxPackageLogic/IMainWindow.cs @@ -124,7 +124,7 @@ void RedrawAllAasxElements( /// Index loaded contents, e.g. for animate of event sending void UiLoadPackageWithNew( PackageCentralItem packItem, - AdminShellPackageFileBasedEnv takeOverEnv = null, + AdminShellPackageEnvBase takeOverEnv = null, string loadLocalFilename = null, string info = null, bool onlyAuxiliary = false, diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index e51cae31a..f7e0f9210 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -1365,7 +1365,7 @@ await DisplayContextPlus.CheckIfDownloadAndStart( // TODO (MIHO, 2022-11-17): not very elegant if (ticket.PostResults != null && ticket.PostResults.ContainsKey("TakeOver") - && ticket.PostResults["TakeOver"] is AdminShellPackageFileBasedEnv pe) + && ticket.PostResults["TakeOver"] is AdminShellPackageEnvBase pe) PackageCentral.MainItem.TakeOver(pe); MainWindow.RestartUIafterNewPackage(); diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index 94f263ffd..31dee3115 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -27,7 +27,6 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Linq; using AdminShellNS.DiaryData; using System.Text.Json; -using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; namespace AasxPackageLogic.PackageCentral { @@ -380,6 +379,60 @@ public override Stream GetThumbnailStreamFromAasOrPackage(string aasId) ms.Seek(0, SeekOrigin.Begin); return ms; } + + public override async Task GetLocalStreamFromPackageAsync( + string uriString, + string aasId = null, + string smId = null, + string idShortPath = null, + FileMode mode = FileMode.Open, + FileAccess access = FileAccess.Read) + { + // IMPORTANT! First try to use the base implementation to get an stream to + // HTTP or ABSOLUTE file + var absStream = base.GetLocalStreamFromPackage(uriString, mode: mode, access: access); + if (absStream != null) + return absStream; + + // ok, try to load from the server + if (aasId?.HasContent() != true || idShortPath?.HasContent() != true + || _defaultRepoBaseUri == null) + return null; + + // try contact repo + var attLoc = PackageContainerHttpRepoSubset.BuildUriForRepoSingleSubmodelAttachment( + _defaultRepoBaseUri, + aasId: aasId, + smId: smId, + idShortPath: idShortPath, + encryptIds: true); + + try + { + Stream res = null; + + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + null, + sourceUri: attLoc, + runtimeOptions: _runtimeOptions, + lambdaDownloadDoneOrFail: (code, ms, contentFn) => + { + // error + if (code != HttpStatusCode.OK && code != HttpStatusCode.NoContent) + return; + + // store (this is stupid!) + res = new MemoryStream(ms.ToByteArray()); + }); + + return res; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + return null; + } + } } } diff --git a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs index 0ee847ccc..4808c3ddf 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs @@ -111,7 +111,7 @@ public bool Load( } } - public bool TakeOver(AdminShellPackageFileBasedEnv env) + public bool TakeOver(AdminShellPackageEnvBase env) { try { diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index b28b86a5a..12bb2b2cc 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -35,6 +35,8 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Dynamic; using Newtonsoft.Json.Linq; using System.ComponentModel; +using Lucene.Net.Util.Automaton; +using RestSharp; namespace AasxPackageLogic.PackageCentral { @@ -389,6 +391,31 @@ public static Uri BuildUriForRepoSingleSubmodel( } } + public static Uri BuildUriForRepoSingleSubmodelAttachment( + Uri baseUri, string smId, + string idShortPath, + bool encryptIds = true, + string aasId = null) + { + // access + if (smId?.HasContent() != true || idShortPath?.HasContent() != true) + return null; + + // smId + var smIdEnc = encryptIds ? AdminShellUtil.Base64UrlEncode(smId) : smId; + + // aasId present? + if (aasId?.HasContent() == true) + { + var aasIdEnc = encryptIds ? AdminShellUtil.Base64UrlEncode(aasId) : aasId; + return CombineUri(baseUri, $"/shells/{aasIdEnc}/submodels/{smIdEnc}/submodel-elements/{idShortPath}/attachment"); + } + else + { + return CombineUri(baseUri, $"/submodels/{smIdEnc}/submodel-elements/{idShortPath}/attachment"); + } + } + public static Uri BuildUriForRepoSingleSubmodel( Uri baseUri, Aas.IReference submodelRef, bool encryptIds = true, @@ -2074,6 +2101,77 @@ public static async Task PerformConnectExtendedDialogue( return true; } + public class FileElementRecord + { + public List Parents; + public Aas.IFile FileSme; + public string IdShortPath; + } + + public static List FindAllUsedFileElements( + Aas.ISubmodel submodel, + Action lambdaReportIdShortPathError = null, + char seperatorChar = '.') + { + // access + var res = new List(); + if (submodel?.SubmodelElements == null) + return res; + + // recurse and add + submodel.RecurseOnSubmodelElements(null, (o, parents, sme) => + { + // File? + if (sme is Aas.IFile file) + { + // check, if there is file content available + if (file?.Value?.HasContent() != true) + { + // do not count as severe failure; just skip + return true; + } + + // build idShort list + var rfs = new List(); + if (parents != null) + rfs.AddRange(parents); + rfs.Add(sme); + var idss = rfs.Select((rf) => "" + rf?.IdShort); + + // check if the elements are fully complying + var comply = true; + foreach (var ids in idss) + { + var test = Regex.Replace(ids, @"[^a-zA-Z0-9_]", "_"); + if (test != ids) + comply = false; + } + + // join and report! + var idsp = string.Join(seperatorChar, idss); + if (!comply) + { + // may report + lambdaReportIdShortPathError?.Invoke(idsp); + // continue with next SME + return true; + } + + // now add + res.Add(new FileElementRecord() { + Parents = parents.Copy(), + FileSme = file, + IdShortPath = idsp + }); + } + + // always go deeper + return true; + }); + + return res; + } + public class UploadAssistantJobRecord { // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; @@ -2086,6 +2184,7 @@ public class UploadAssistantJobRecord public bool IncludeSubmodels = false; public bool IncludeCDs = false; + public bool IncludeSupplFiles = false; public bool OverwriteIfExist = false; } @@ -2251,6 +2350,17 @@ public static async Task PerformUploadAssistant( row++; + // Include supplementary files + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Include supplementary files for File elements", + isChecked: recordJob.IncludeSupplFiles, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { recordJob.IncludeSupplFiles = b; }); + + row++; + // Overwrite if exists? AnyUiUIElement.SetBoolFromControl( helper.Set( @@ -2445,6 +2555,8 @@ public static async Task PerformUploadAssistant( .Count(); numOK = 0; numNOK = 0; + var numAttOK = 0; + var numAttNOK = 0; // setup worker var workerUpload = new BackgroundWorker(); @@ -2477,6 +2589,10 @@ public static async Task PerformUploadAssistant( aasId = aas?.Id; } + // + // Identifiable + // + // location Uri location = null; if (idf is Aas.IAssetAdministrationShell) @@ -2489,6 +2605,7 @@ public static async Task PerformUploadAssistant( if (location == null) return; + // put Identifiable var res2 = await PackageHttpDownloadUtil.HttpPutPostIdentifiable( client, idf, @@ -2497,13 +2614,100 @@ public static async Task PerformUploadAssistant( runtimeOptions: runtimeOptions, containerList: containerList); + // + // attachments (for Submodels) + // + + if (idf is Aas.ISubmodel submodel && submodel.SubmodelElements != null) + { + // Note: the Part 2 PDF says '/', the swagger says '.' + var filEls = FindAllUsedFileElements(submodel, + seperatorChar: '.', + lambdaReportIdShortPathError: (idsp) => + { + Log.Singleton.Error("When uploading Submodel {0}, idShort path for File " + + "elements contains invalid characters and prevents uploading file " + + "attchment: {1}", submodel.IdShort, idsp); + lock (rowsToUpload) + { + numAttNOK++; + } + }); + + foreach (var filEl in filEls) + { + // access + if (filEl?.FileSme?.Value?.HasContent() != true) + continue; + + // try read the bytes (should have try/catch in it) + var ba = await packEnv.GetByteArrayFromExternalInternalUri(filEl.FileSme.Value); + if (ba == null || ba.Length < 1) + { + Log.Singleton.Error("Centralize file: cannot read file: {0}", filEl.FileSme.Value); + lock (rowsToUpload) + { + numAttNOK++; + } + continue; + } + + // try PUT + try + { + // serialize to memory stream + var attLoc = BuildUriForRepoSingleSubmodelAttachment( + baseUri, submodel.Id, + idShortPath: filEl.IdShortPath, + encryptIds: true, + aasId: aasId); + using (var ms = new MemoryStream(ba)) + { + // write + var res3 = await PackageHttpDownloadUtil.HttpPutPostFromMemoryStream( + client, + ms, + destUri: attLoc, + runtimeOptions: runtimeOptions, + containerList: containerList, + usePost: usePost); + + lock (rowsToUpload) + { + if (res3.Item1 != HttpStatusCode.OK && res3.Item1 != HttpStatusCode.NoContent) + { + Log.Singleton.Error("Error uploading attachment of {0} bytes to: {1}", + ba.Length, attLoc.ToString()); + numAttNOK++; + } + else + { + numAttOK++; + } + } + + } + } + catch (Exception ex) + { + Log.Singleton.Error(ex, + $"when PUTting attachment with {ba.Length} bytes to File element {filEl.IdShortPath}"); + numAttNOK++; + } + } + } + + // // mutex (after all async calls!) + // + lock (rowsToUpload) { Action lambdaStat = (ok) => { if (ok) { numOK++; } else { numNOK++; }; ucProgUpload.Info = $"{numOK} entities OK, {numNOK} entities NOT OK of {numTotal}\n" + + $"{numAttOK} attachments OK, {numAttNOK} attachments NOT OK\n" + $"Id: {idf?.Id}"; ucProgUpload.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); }; diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs index 25d864902..dccbb53c1 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs @@ -190,7 +190,7 @@ public void SaveAsLocalFile(string fn) } } - public void AddByAasPackage(PackageCentral packageCentral, AdminShellPackageFileBasedEnv env, string fn) + public void AddByAasPackage(PackageCentral packageCentral, AdminShellPackageEnvBase env, string fn) { // access if (env == null) @@ -243,7 +243,7 @@ public virtual void DeletePackageFromServer(PackageContainerRepoItem repoItem) // Converters & generators // - public void PopulateFakePackage(AdminShellPackageFileBasedEnv pkg) + public void PopulateFakePackage(AdminShellPackageEnvBase pkg) { // access if (pkg == null) diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index e59c49a65..75bd1f421 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -650,6 +650,32 @@ public static async Task> HttpPutPostIdentifiable( } } + public static async Task> HttpPutPostBytes( + HttpClient reUseClient, + byte[] ba, + Uri destUri, + bool usePost = false, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null) + { + // access + if (ba == null || destUri == null) + return null; + + // serialize to memory stream + using (var ms = new MemoryStream(ba)) + { + // write + return await PackageHttpDownloadUtil.HttpPutPostFromMemoryStream( + reUseClient, + ms, + destUri: destUri, + runtimeOptions: runtimeOptions, + containerList: containerList, + usePost: usePost); + } + } + /// /// This utility is able to parallel download Identifiables and will call lambda upon. /// Insted of a list of location, it is taking a list of objects (entities) and a lambda diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs index a1a4756fd..2f4117f7d 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs @@ -34,7 +34,7 @@ public class AssetInterfaceAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private AssetInterfaceOptions _options = null; private PluginEventStack _eventStack = null; @@ -85,7 +85,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, AssetInterfaceOptions theOptions, PluginEventStack eventStack, @@ -142,7 +142,7 @@ public static AssetInterfaceAnyUiControl FillWithAnyUiControls( AidAllInterfaceStatus ifxStatus) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -166,7 +166,7 @@ public static AssetInterfaceAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // test trivial access @@ -186,7 +186,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, IEnumerable foundRecs, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // make an outer grid, very simple grid of two rows: header & body @@ -268,7 +268,7 @@ protected AnyUiLambdaActionBase TriggerUpdate(bool full = true) protected void RenderPanelInner( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, AssetInterfaceOptionsRecord rec, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // access diff --git a/src/AasxPluginBomStructure/AasReferenceStore.cs b/src/AasxPluginBomStructure/AasReferenceStore.cs index 0f6513057..8d7a1b8cd 100644 --- a/src/AasxPluginBomStructure/AasReferenceStore.cs +++ b/src/AasxPluginBomStructure/AasReferenceStore.cs @@ -63,6 +63,9 @@ protected uint ComputeHashOnReference(Aas.IReference r) { foreach (var k in r.Keys) { + if (k?.Value == null) + continue; + // DO NOT include the Type into the hash, as this would render it impossible // to find CDs with either "ConceptDescription" / "GlobalReference" //// var bs = BitConverter.GetBytes((int) k.Type); diff --git a/src/AasxPluginBomStructure/GenericBomControl.cs b/src/AasxPluginBomStructure/GenericBomControl.cs index da0c4979f..903bfaaac 100644 --- a/src/AasxPluginBomStructure/GenericBomControl.cs +++ b/src/AasxPluginBomStructure/GenericBomControl.cs @@ -36,7 +36,7 @@ namespace AasxPluginBomStructure /// public class GenericBomControl { - private AdminShellPackageFileBasedEnv _package; + private AdminShellPackageEnvBase _package; private Aas.Submodel _submodel; private bool _createOnPackage = false; @@ -244,7 +244,7 @@ public object FillWithWpfControls( object opackage, object osm, object masterDockPanel) { // access - _package = opackage as AdminShellPackageFileBasedEnv; + _package = opackage as AdminShellPackageEnvBase; _submodel = osm as Aas.Submodel; _createOnPackage = false; _bomOptions = bomOptions; @@ -979,7 +979,7 @@ public object CreateViewPackageReleations( DockPanel master) { // access - _package = opackage as AdminShellPackageFileBasedEnv; + _package = opackage as AdminShellPackageEnvBase; _submodel = null; _createOnPackage = true; _bomOptions = bomOptions; @@ -1047,7 +1047,7 @@ public object CreateViewPackageReleations( } private Microsoft.Msagl.Drawing.Graph CreateGraph( - AdminShellPackageFileBasedEnv env, + AdminShellPackageEnvBase env, Aas.Submodel sm, GenericBomCreatorOptions options, bool createOnPackage = false) diff --git a/src/AasxPluginContactInformation/ContactEntity.cs b/src/AasxPluginContactInformation/ContactEntity.cs index cb0e8a644..3aebb2a27 100644 --- a/src/AasxPluginContactInformation/ContactEntity.cs +++ b/src/AasxPluginContactInformation/ContactEntity.cs @@ -117,7 +117,7 @@ public class ListOfContactEntity : List // public static ListOfContactEntity ParseSubmodelForV10( - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel subModel, AasxPluginContactInformation.ContactInformationOptions options, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) diff --git a/src/AasxPluginContactInformation/ContactListAnyUiControl.cs b/src/AasxPluginContactInformation/ContactListAnyUiControl.cs index f3e120af0..b005dea1c 100644 --- a/src/AasxPluginContactInformation/ContactListAnyUiControl.cs +++ b/src/AasxPluginContactInformation/ContactListAnyUiControl.cs @@ -35,7 +35,7 @@ public class ShelfAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private ContactInformationOptions _options = null; private PluginEventStack _eventStack = null; @@ -80,7 +80,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, ContactInformationOptions theOptions, PluginEventStack eventStack, @@ -120,7 +120,7 @@ public static ShelfAnyUiControl FillWithAnyUiControls( AasxPluginBase plugin) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) diff --git a/src/AasxPluginDocumentShelf/DocumentEntity.cs b/src/AasxPluginDocumentShelf/DocumentEntity.cs index 9d5db49d5..be7b72db2 100644 --- a/src/AasxPluginDocumentShelf/DocumentEntity.cs +++ b/src/AasxPluginDocumentShelf/DocumentEntity.cs @@ -173,7 +173,7 @@ public class ListOfDocumentEntity : List // public static ListOfDocumentEntity ParseSubmodelForV10( - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel subModel, AasxPluginDocumentShelf.DocumentShelfOptions options, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) @@ -345,7 +345,7 @@ private static void SearchForRelations( } public static ListOfDocumentEntity ParseSubmodelForV11( - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel subModel, AasxPredefinedConcepts.VDI2770v11 defs11, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) @@ -549,7 +549,7 @@ public static ListOfDocumentEntity ParseSubmodelForV11( } public static ListOfDocumentEntity ParseSubmodelForV12( - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel subModel, AasxPredefinedConcepts.IdtaHandoverDocumentationV12 defs12, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) diff --git a/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs b/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs index c84a75825..801958453 100644 --- a/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs +++ b/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs @@ -35,7 +35,7 @@ public class ShelfAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private DocumentShelfOptions _options = null; private PluginEventStack _eventStack = null; @@ -144,7 +144,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, DocumentShelfOptions theOptions, PluginEventStack eventStack, @@ -187,7 +187,7 @@ public static ShelfAnyUiControl FillWithAnyUiControls( ShelfPreviewService previewService) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) diff --git a/src/AasxPluginDocumentShelf/ShelfPreviewService.cs b/src/AasxPluginDocumentShelf/ShelfPreviewService.cs index 5bf62ae6f..06d32a836 100644 --- a/src/AasxPluginDocumentShelf/ShelfPreviewService.cs +++ b/src/AasxPluginDocumentShelf/ShelfPreviewService.cs @@ -63,11 +63,11 @@ public class RenderEntity public AnyUiBitmapInfo Bitmap = null; - public AdminShellPackageFileBasedEnv Package = null; + public AdminShellPackageEnvBase Package = null; public DateTime LastUse = DateTime.Now; - public RenderEntity(AdminShellPackageFileBasedEnv package, string supplFn) + public RenderEntity(AdminShellPackageEnvBase package, string supplFn) { Package = package; PackageFn = package?.Filename; diff --git a/src/AasxPluginGenericForms/GenericFormsAnyUiControl.cs b/src/AasxPluginGenericForms/GenericFormsAnyUiControl.cs index 063ae8a0d..01995cda6 100644 --- a/src/AasxPluginGenericForms/GenericFormsAnyUiControl.cs +++ b/src/AasxPluginGenericForms/GenericFormsAnyUiControl.cs @@ -28,7 +28,7 @@ public class GenericFormsAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private AasxPluginGenericForms.GenericFormOptions _options = null; private PluginEventStack _eventStack = null; @@ -48,7 +48,7 @@ public class GenericFormsAnyUiControl public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, AasxPluginGenericForms.GenericFormOptions theOptions, PluginEventStack eventStack, @@ -76,7 +76,7 @@ public static GenericFormsAnyUiControl FillWithAnyUiControls( PluginOperationContextBase opContext) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) diff --git a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs index 63a739470..8c00b280a 100644 --- a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs +++ b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs @@ -25,6 +25,9 @@ This source code may use other Open Source software components (see LICENSE.txt) using Extensions; using AnyUi; using Newtonsoft.Json; +using System.IO.Packaging; +using System.IO; +using System.Net.Http; // ReSharper disable NegativeEqualityExpression // ReSharper disable AccessToModifiedClosure @@ -374,6 +377,10 @@ protected void SetBasicInfos() // image AnyUiBitmapInfo bi = null; + + // need parent information + _submodel.SetAllParents(); + var aas = _package.AasEnv?.FindAasWithSubmodelId(_submodel.Id); // file? var fe = _submodel.SubmodelElements.FindFirstSemanticIdAs( @@ -381,7 +388,17 @@ protected void SetBasicInfos() MatchMode.Relaxed); if (fe?.Value != null) { - bi = AnyUiGdiHelper.LoadBitmapInfoFromPackage(_package, fe.Value); + var idShortPath = "" + fe.CollectIdShortByParent( + separatorChar: '.', excludeIdentifiable: true); + var task = Task.Run(() => _package.GetLocalStreamFromPackageAsync( + uriString: fe.Value, + aasId: "" + aas?.Id, + smId: "" + _submodel.Id, + idShortPath: idShortPath)); + task.Wait(); + var stream = task.Result; + + bi = AnyUiGdiHelper.LoadBitmapInfoFromStream(stream); } // BLOB diff --git a/src/AasxPluginKnownSubmodels/KnownSubmodelAnyUiControl.cs b/src/AasxPluginKnownSubmodels/KnownSubmodelAnyUiControl.cs index 040f60c96..6f644fb38 100644 --- a/src/AasxPluginKnownSubmodels/KnownSubmodelAnyUiControl.cs +++ b/src/AasxPluginKnownSubmodels/KnownSubmodelAnyUiControl.cs @@ -27,7 +27,7 @@ public class KnownSubmodelAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private KnownSubmodelsOptions _options = null; private PluginEventStack _eventStack = null; @@ -58,7 +58,7 @@ public KnownSubmodelAnyUiControl() public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, KnownSubmodelsOptions theOptions, PluginEventStack eventStack, @@ -83,7 +83,7 @@ public static KnownSubmodelAnyUiControl FillWithAnyUiControls( object opanel) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -107,7 +107,7 @@ public static KnownSubmodelAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // test trivial access @@ -127,7 +127,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, IEnumerable foundRecs, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // make an outer grid, very simple grid of two rows: header & body @@ -197,7 +197,7 @@ protected void RenderPanelOutside( protected void RenderPanelInner( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, KnownSubmodelsOptionsRecord rec, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // access diff --git a/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs b/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs index 9054da930..601d47cb3 100644 --- a/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs +++ b/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs @@ -32,7 +32,7 @@ public partial class WpfMtpControlWrapper : UserControl // internal members private LogInstance theLog = null; - private AdminShellPackageFileBasedEnv thePackage = null; + private AdminShellPackageEnvBase thePackage = null; private Aas.Submodel theSubmodel = null; private AasxPluginMtpViewer.MtpViewerOptions theOptions = null; private PluginEventStack theEventStack = null; @@ -67,7 +67,7 @@ public WpfMtpControlWrapper() } public void Start( - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, AasxPluginMtpViewer.MtpViewerOptions theOptions, PluginEventStack eventStack, @@ -88,7 +88,7 @@ public static WpfMtpControlWrapper FillWithWpfControls( object masterDockPanel) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var master = masterDockPanel as DockPanel; if (package == null || sm == null || master == null) @@ -292,6 +292,9 @@ private bool CheckIfPackageFile(string fn) private void LoadFile(string fn) { + // access + if (fn?.HasContent() != true) + return; if (!".aml .zip .mtp".Contains(System.IO.Path.GetExtension(fn.Trim().ToLower()))) return; diff --git a/src/AasxPluginPlotting/PlotItem.cs b/src/AasxPluginPlotting/PlotItem.cs index 4b3781728..8737afb79 100644 --- a/src/AasxPluginPlotting/PlotItem.cs +++ b/src/AasxPluginPlotting/PlotItem.cs @@ -274,7 +274,7 @@ public void UpdateValues(string lang = null) } public void RebuildFromSubmodel( - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm, string lang) { // clear & access diff --git a/src/AasxPluginPlotting/PlottingViewControl.xaml.cs b/src/AasxPluginPlotting/PlottingViewControl.xaml.cs index 7798ee36f..0ae20ad7f 100644 --- a/src/AasxPluginPlotting/PlottingViewControl.xaml.cs +++ b/src/AasxPluginPlotting/PlottingViewControl.xaml.cs @@ -39,7 +39,7 @@ public PlottingViewControl() InitializeComponent(); } - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private PlottingOptions _options = null; private PluginEventStack _pluginEvents = null; @@ -52,7 +52,7 @@ public PlottingViewControl() private string _defaultLang = null; public void Start( - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm, PlottingOptions options, PluginEventStack pluginEvents, diff --git a/src/AasxPluginPlotting/Plugin.cs b/src/AasxPluginPlotting/Plugin.cs index 61e23bfb8..6d42d206c 100644 --- a/src/AasxPluginPlotting/Plugin.cs +++ b/src/AasxPluginPlotting/Plugin.cs @@ -167,7 +167,7 @@ public class AasxPlugin : AasxPluginBase if (action == "fill-panel-visual-extension" && args != null && args.Length >= 3) { // access - var package = args[0] as AdminShellPackageFileBasedEnv; + var package = args[0] as AdminShellPackageEnvBase; var sm = args[1] as Aas.Submodel; var master = args[2] as DockPanel; if (package == null || sm == null || master == null) diff --git a/src/AasxPluginProductChangeNotifications/PcnAnyUiControl.cs b/src/AasxPluginProductChangeNotifications/PcnAnyUiControl.cs index 86692139f..beb3f69b9 100644 --- a/src/AasxPluginProductChangeNotifications/PcnAnyUiControl.cs +++ b/src/AasxPluginProductChangeNotifications/PcnAnyUiControl.cs @@ -41,7 +41,7 @@ public class PcnAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; + private AdminShellPackageEnvBase _package = null; private Aas.Submodel _submodel = null; private PcnOptions _options = null; private PluginEventStack _eventStack = null; @@ -87,7 +87,7 @@ public void Dispose() public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, + AdminShellPackageEnvBase thePackage, Aas.Submodel theSubmodel, PcnOptions theOptions, PluginEventStack eventStack, @@ -122,7 +122,7 @@ public static PcnAnyUiControl FillWithAnyUiControls( AnyUiContextBase displayContext) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -146,7 +146,7 @@ public static PcnAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // test trivial access @@ -172,7 +172,7 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, IEnumerable foundRecs, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm, PDPCN.CD_ProductChangeNotifications data) { @@ -1139,7 +1139,7 @@ protected void InnerDocAddTechnicalDataCompare( } protected void InnerDocIdentificationData( - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, AnyUiSmallWidgetToolkit uitk, AnyUiGrid grid, string header, @@ -1223,7 +1223,7 @@ protected void InnerDocAddProductClassifications( protected void RenderPanelInner( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, PcnOptionsRecord rec, - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm, PDPCN.CD_Record data) { @@ -1576,7 +1576,7 @@ public void Update(params object[] args) //================= protected async Task AddFromSmartPcnXml( - AdminShellPackageFileBasedEnv package, + AdminShellPackageEnvBase package, Aas.Submodel sm) { // access diff --git a/src/AasxPluginTechnicalData/TechnicalDataAnyUiControl.cs b/src/AasxPluginTechnicalData/TechnicalDataAnyUiControl.cs index 758e3cb49..b548e19bb 100644 --- a/src/AasxPluginTechnicalData/TechnicalDataAnyUiControl.cs +++ b/src/AasxPluginTechnicalData/TechnicalDataAnyUiControl.cs @@ -36,8 +36,9 @@ public class TechnicalDataAnyUiControl //============= private LogInstance _log = new LogInstance(); - private AdminShellPackageFileBasedEnv _package = null; - private Aas.Submodel _submodel = null; + private AdminShellPackageEnvBase _package = null; + private Aas.IAssetAdministrationShell _aas = null; + private Aas.ISubmodel _submodel = null; private TechnicalDataOptions _options = null; private PluginEventStack _eventStack = null; private AnyUiStackPanel _panel = null; @@ -45,6 +46,9 @@ public class TechnicalDataAnyUiControl protected AnyUiSmallWidgetToolkit _uitk = new AnyUiSmallWidgetToolkit(); + protected AnyUiGdiHelper.DelayedFileContentLoader _bitmapLoader = + new AnyUiGdiHelper.DelayedFileContentLoader(); + #endregion #region Members to be kept for state/ update @@ -63,13 +67,15 @@ public class TechnicalDataAnyUiControl // ReSharper disable EmptyConstructor public TechnicalDataAnyUiControl() { + // start a timer + AnyUiTimerHelper.CreatePluginTimerAsync(300, lambdaTick: async () => await DispatcherTimer_Tick(null, null)); } // ReSharper enable EmptyConstructor public void Start( LogInstance log, - AdminShellPackageFileBasedEnv thePackage, - Aas.Submodel theSubmodel, + AdminShellPackageEnvBase thePackage, + Aas.ISubmodel theSubmodel, TechnicalDataOptions theOptions, PluginEventStack eventStack, AnyUiStackPanel panel, @@ -78,6 +84,7 @@ public void Start( _log = log; _package = thePackage; _submodel = theSubmodel; + _aas = _package?.AasEnv?.FindAasWithSubmodelId(_submodel?.Id); _options = theOptions; _eventStack = eventStack; _panel = panel; @@ -96,7 +103,7 @@ public static TechnicalDataAnyUiControl FillWithAnyUiControls( AasxPluginBase plugin) { // access - var package = opackage as AdminShellPackageFileBasedEnv; + var package = opackage as AdminShellPackageEnvBase; var sm = osm as Aas.Submodel; var panel = opanel as AnyUiStackPanel; if (package == null || sm == null || panel == null) @@ -120,8 +127,8 @@ public static TechnicalDataAnyUiControl FillWithAnyUiControls( private void RenderFullView( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, - AdminShellPackageFileBasedEnv package, - Aas.Submodel sm, string defaultLang = null) + AdminShellPackageEnvBase package, + Aas.ISubmodel sm, string defaultLang = null) { // test trivial access if (_options == null || _submodel?.SemanticId == null) @@ -146,8 +153,8 @@ private void RenderFullView( protected void RenderPanelOutside( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, ConceptModelZveiTechnicalData theDefs, - AdminShellPackageFileBasedEnv package, - Aas.Submodel sm, string defaultLang = null) + AdminShellPackageEnvBase package, + Aas.ISubmodel sm, string defaultLang = null) { // make an outer grid, very simple grid of two rows: header & body var outer = view.Add(uitk.AddSmallGrid(rows: 7, cols: 1, colWidths: new[] { "*" })); @@ -282,8 +289,8 @@ protected class ClassificationRecord protected void RenderPanelHeader( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, ConceptModelZveiTechnicalData theDefs, - AdminShellPackageFileBasedEnv package, - Aas.Submodel sm, string defaultLang = null) + AdminShellPackageEnvBase package, + Aas.ISubmodel sm, string defaultLang = null) { // access if (view == null || uitk == null || sm == null) @@ -328,7 +335,8 @@ protected void RenderPanelHeader( smcGeneral.Value.FindFirstSemanticIdAs( theDefs.CD_ManufacturerName.GetSingleKey(), MatchMode.Relaxed)?.Value; - AnyUiBitmapInfo imageManuLogo = null; + AnyUiBitmapInfo biManuLogo = null; + AnyUiImage imageManuLogo = null; if (package != null) { #if USE_WPF @@ -339,10 +347,20 @@ protected void RenderPanelHeader( ); imageManuLogo = AnyUiHelper.CreateAnyUiBitmapInfo(bi); #else - imageManuLogo = AnyUiGdiHelper.LoadBitmapInfoFromPackage(package, - smcGeneral.Value.FindFirstSemanticIdAs( - theDefs.CD_ManufacturerLogo.GetSingleKey(), MatchMode.Relaxed)?.Value - ); + { + // File + var fe = smcGeneral.Value.FindFirstSemanticIdAs( + theDefs.CD_ManufacturerLogo.GetSingleKey(), MatchMode.Relaxed); + + // for now + biManuLogo = AnyUiGdiHelper.LoadBitmapInfoFromPackage(package, fe?.Value); + + // for later + _bitmapLoader.Add(package, _aas, _submodel, fe, (fcl, bi) => + { + imageManuLogo.BitmapInfo = bi; + }); + } #endif } @@ -371,11 +389,11 @@ protected void RenderPanelHeader( setBold: false, content: manuName); - uitk.Set( + imageManuLogo = uitk.Set( uitk.AddSmallImageTo(outer, 1, 1, margin: new AnyUiThickness(2), stretch: AnyUiStretch.Uniform, - bitmap: imageManuLogo), + bitmap: biManuLogo), rowSpan: 2, maxHeight: 100, maxWidth: 200, horizontalAlignment: AnyUiHorizontalAlignment.Stretch, @@ -385,44 +403,41 @@ protected void RenderPanelHeader( // Product Images // - // gather + // list of file elements + var fePIs = smcGeneral.Value.FindAllSemanticIdAs( + theDefs.CD_ProductImage.GetSingleKey(), MatchMode.Relaxed).ToList(); - var pil = new List(); - foreach (var pi in - smcGeneral.Value.FindAllSemanticIdAs( - theDefs.CD_ProductImage.GetSingleKey(), MatchMode.Relaxed)) + // make an outer grid, very simple grid of two rows: header & body + var pilGrid = uitk.AddSmallGridTo(outer, 3, 0, rows: 1, cols: fePIs.Count); + + int col = 0; + foreach (var fePI in fePIs) { + var imageElem = uitk.Set( + uitk.AddSmallImageTo(pilGrid, 0, col++, + margin: new AnyUiThickness(2), + stretch: AnyUiStretch.Uniform), + maxHeight: 100, maxWidth: 200, + horizontalAlignment: AnyUiHorizontalAlignment.Stretch, + verticalAlignment: AnyUiVerticalAlignment.Stretch); + #if USE_WPF var bi = AasxWpfBaseUtils.LoadBitmapImageFromPackage(package, pi.value); var imgInfo = AnyUiHelper.CreateAnyUiBitmapInfo(bi); if (imgInfo != null) - pil.Add(imgInfo); + thisImage.BitmapInfo = bi; #else - var imgInfo = AnyUiGdiHelper.LoadBitmapInfoFromPackage(package, pi.Value); - if (imgInfo != null) - pil.Add(imgInfo); -#endif - } + // for now + imageElem.BitmapInfo = AnyUiGdiHelper.LoadBitmapInfoFromPackage(package, fePI.Value); - // ok, render? - - if (pil.Count > 0) - { - // make an outer grid, very simple grid of two rows: header & body - var pilGrid = uitk.AddSmallGridTo(outer, 3, 0, rows: 1, cols: pil.Count); - - for (int i = 0; i < pil.Count; i++) + // for later + _bitmapLoader.Add(package, _aas, _submodel, fePI, (fcl, bi) => { - uitk.Set( - uitk.AddSmallImageTo(pilGrid, 0, 0 + i, - margin: new AnyUiThickness(2), - stretch: AnyUiStretch.Uniform, - bitmap: pil[i]), - maxHeight: 100, maxWidth: 200, - horizontalAlignment: AnyUiHorizontalAlignment.Stretch, - verticalAlignment: AnyUiVerticalAlignment.Stretch); - } + imageElem.BitmapInfo = bi; + }); +#endif } + } // @@ -443,15 +458,15 @@ protected void RenderPanelHeader( var sys = ( "" + smc.Value.FindFirstSemanticIdAs( - theDefs.CD_ProductClassificationSystem.GetSingleKey(), MatchMode.Relaxed)?.Value).Trim(); + theDefs.CD_ProductClassificationSystem.GetSingleKey(), MatchMode.Relaxed)?.Value)?.Trim(); var ver = ( "" + smc.Value.FindFirstSemanticIdAs( - theDefs.CD_ClassificationSystemVersion.GetSingleKey(), MatchMode.Relaxed)?.Value).Trim(); + theDefs.CD_ClassificationSystemVersion.GetSingleKey(), MatchMode.Relaxed)?.Value)?.Trim(); var cls = ( "" + smc.Value.FindFirstSemanticIdAs( - theDefs.CD_ProductClassId.GetSingleKey(), MatchMode.Relaxed)?.Value).Trim(); + theDefs.CD_ProductClassId.GetSingleKey(), MatchMode.Relaxed)?.Value)?.Trim(); if (sys != "" && cls != "") clr.Add(new ClassificationRecord() { System = sys, Version = ver, ClassTxt = cls }); @@ -563,7 +578,7 @@ protected void RenderTripleRowData( } protected void TableAddPropertyRows_Recurse( - ConceptModelZveiTechnicalData theDefs, string defaultLang, AdminShellPackageFileBasedEnv package, + ConceptModelZveiTechnicalData theDefs, string defaultLang, AdminShellPackageEnvBase package, List rows, List smec, int depth = 0) { // access @@ -714,8 +729,8 @@ protected void TableAddPropertyRows_Recurse( protected void RenderPanelInner( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, ConceptModelZveiTechnicalData theDefs, - AdminShellPackageFileBasedEnv package, - Aas.Submodel sm, string defaultLang = null) + AdminShellPackageEnvBase package, + Aas.ISubmodel sm, string defaultLang = null) { // access if (view == null || uitk == null || sm == null) @@ -753,8 +768,8 @@ protected void RenderPanelInner( protected void RenderPanelFooter( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, ConceptModelZveiTechnicalData theDefs, - AdminShellPackageFileBasedEnv package, - Aas.Submodel sm, string defaultLang = null) + AdminShellPackageEnvBase package, + Aas.ISubmodel sm, string defaultLang = null) { // access if (view == null || uitk == null || sm == null) @@ -822,6 +837,19 @@ public void Update(params object[] args) #region Callbacks //=============== + private async Task DispatcherTimer_Tick(object sender, EventArgs e) + { + if ((await _bitmapLoader?.TickToLoad()) == true) + { + _eventStack?.PushEvent(new AasxPluginEventReturnUpdateAnyUi() + { + PluginName = null, + Mode = AnyUiRenderMode.StatusToUi, + UseInnerGrid = true + }); + } + } + #endregion diff --git a/src/AasxPredefinedConcepts/ConceptModel/ConceptModelZveiTechnicalData.cs b/src/AasxPredefinedConcepts/ConceptModel/ConceptModelZveiTechnicalData.cs index 116f4295c..6ab1988e4 100644 --- a/src/AasxPredefinedConcepts/ConceptModel/ConceptModelZveiTechnicalData.cs +++ b/src/AasxPredefinedConcepts/ConceptModel/ConceptModelZveiTechnicalData.cs @@ -47,7 +47,7 @@ public Aas.ConceptDescription CD_TextStatement, CD_ValidDate; - public ConceptModelZveiTechnicalData(Aas.Submodel sm) + public ConceptModelZveiTechnicalData(Aas.ISubmodel sm) { InitFromSubmodel(sm); } @@ -155,7 +155,7 @@ public void InitFromVersion(Version ver) } } - public void InitFromSubmodel(Aas.Submodel sm) + public void InitFromSubmodel(Aas.ISubmodel sm) { var defsV10 = new AasxPredefinedConcepts.DefinitionsZveiTechnicalData.SetOfDefs( new AasxPredefinedConcepts.DefinitionsZveiTechnicalData()); diff --git a/src/BlazorExplorer/Data/AASService.cs b/src/BlazorExplorer/Data/AASService.cs index 4f0a39693..6c0b1efe9 100644 --- a/src/BlazorExplorer/Data/AASService.cs +++ b/src/BlazorExplorer/Data/AASService.cs @@ -95,7 +95,7 @@ public Item FindReferable(Aas.IReferable rf, string pluginTag) foreach (var ch in res.Childs) if (ch != null && ch.Type == "Plugin" - && ch.Tag is Tuple tag && tag?.Item4?.Tag?.Trim().ToLower() == pluginTag.Trim().ToLower()) return ch; @@ -116,7 +116,7 @@ public Item FindSubmodelPlugin(string smid, string pluginTag) return false; return parit.Referable is Aas.Submodel sm && sm?.Id == smid && it.Type == "Plugin" - && it.Tag is Tuple tag && tag?.Item4?.Tag?.Trim().ToLower() == pluginTag.Trim().ToLower(); }).FirstOrDefault(); @@ -247,7 +247,7 @@ public void buildTree(BlazorSession bi) Referable = sm, envIndex = i, Text = "PLUGIN", - Tag = new Tuple (bi.env, sm, lpi, ext), Type = "Plugin" diff --git a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs index 144a14e3e..3acfe1715 100644 --- a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs +++ b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs @@ -125,7 +125,7 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() public void UiLoadPackageWithNew( PackageCentralItem packItem, - AdminShellPackageFileBasedEnv takeOverEnv = null, + AdminShellPackageEnvBase takeOverEnv = null, string loadLocalFilename = null, string info = null, bool onlyAuxiliary = false, diff --git a/src/BlazorExplorer/Data/BlazorSession.cs b/src/BlazorExplorer/Data/BlazorSession.cs index 60653b76a..0384169b5 100644 --- a/src/BlazorExplorer/Data/BlazorSession.cs +++ b/src/BlazorExplorer/Data/BlazorSession.cs @@ -168,7 +168,7 @@ public PackageCentral PackageCentral // dead-csharp on // old stuff, to be refactored - public AdminShellPackageFileBasedEnv env = null; + public AdminShellPackageEnvBase env = null; public IndexOfSignificantAasElements significantElements = null; public string[] aasxFiles = new string[1]; From 4fd6e637ffc57350e50abf5aaade41c746e4fda6 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Tue, 5 Nov 2024 19:29:35 +0100 Subject: [PATCH 28/99] * update --- .../AdminShellPackageEnvBase.cs | 739 ++++++++++++++++++ .../AdminShellPackageFileBasedEnv.cs | 706 +---------------- .../Extensions/ExtendEnvironment.cs | 32 +- .../Extensions/ExtendSubmodel.cs | 2 +- .../AnyUI/AnyUiMagickHelper.cs | 5 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 40 + src/AasxPackageExplorer/debug.MIHO.script | 3 +- .../options-debug.MIHO.json | 2 +- src/AasxPackageLogic/DispEditHelperModules.cs | 2 +- .../MainWindowAnyUiDialogs.cs | 1 + .../AdminShellPackageDynamicFetchEnv.cs | 1 + .../PackageContainerHttpRepoSubset.cs | 45 +- src/AasxPackageLogic/VisualAasxElements.cs | 9 +- src/AasxPluginDocumentShelf/DocumentEntity.cs | 40 +- .../ShelfAnyUiControl.cs | 36 +- .../ShelfPreviewService.cs | 20 +- .../ImageMapAnyUiControl.cs | 24 +- .../WpfMtpControlWrapper.xaml.cs | 210 ++--- src/AasxWpfControlLibrary/AnyUiWpf.cs | 18 +- 19 files changed, 1106 insertions(+), 829 deletions(-) create mode 100644 src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs diff --git a/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs b/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs new file mode 100644 index 000000000..fb91df6b0 --- /dev/null +++ b/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs @@ -0,0 +1,739 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + +using Extensions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Packaging; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection.Metadata; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace AdminShellNS +{ + /// + /// This class lets an outer functionality keep track on the supplementary files, which are in or + /// are pending to be added or deleted to an Package. + /// + public class AdminShellPackageSupplementaryFile /*: IReferable*/ + { + public delegate byte[] SourceGetByteChunk(); + + public enum LocationType { InPackage, AddPending, DeletePending } + + public enum SpecialHandlingType { None, EmbedAsThumbnail } + + public readonly Uri Uri = null; + + public readonly string UseMimeType = null; + + public readonly string SourceLocalPath = null; + public readonly SourceGetByteChunk SourceGetBytesDel = null; + + public LocationType Location; + public readonly SpecialHandlingType SpecialHandling; + + public AdminShellPackageSupplementaryFile( + Uri uri, string sourceLocalPath = null, LocationType location = LocationType.InPackage, + SpecialHandlingType specialHandling = SpecialHandlingType.None, + SourceGetByteChunk sourceGetBytesDel = null, string useMimeType = null) + { + Uri = uri; + UseMimeType = useMimeType; + SourceLocalPath = sourceLocalPath; + SourceGetBytesDel = sourceGetBytesDel; + Location = location; + SpecialHandling = specialHandling; + } + + // class derives from Referable in order to provide GetElementName + public string GetElementName() + { + return "File"; + } + + } + + public class ListOfAasSupplementaryFile : List + { + public AdminShellPackageSupplementaryFile FindByUri(string path) + { + if (path == null) + return null; + + return this.FirstOrDefault( + x => x?.Uri?.ToString().Trim() == path.Trim()); + } + } + + /// + /// Provides (static?) helpers for serializing AAS.. + /// + public static class AdminShellSerializationHelper + { + + public static string TryReadXmlFirstElementNamespaceURI(Stream s) + { + string res = null; + try + { + var xr = System.Xml.XmlReader.Create(s); + int i = 0; + while (xr.Read()) + { + // limit amount of read + i++; + if (i > 99) + // obviously not found + break; + + // find element + if (xr.NodeType == System.Xml.XmlNodeType.Element) + { + res = xr.NamespaceURI; + break; + } + } + xr.Close(); + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + + // return to zero pos + s.Seek(0, SeekOrigin.Begin); + + // give back + return res; + } + + /// + /// Skips first few tokens of an XML content until first "real" element is encountered + /// + /// + public static void XmlSkipHeader(XmlReader xmlReader) + { + while (xmlReader.NodeType == XmlNodeType.XmlDeclaration || + xmlReader.NodeType == XmlNodeType.Whitespace || + xmlReader.NodeType == XmlNodeType.Comment || + xmlReader.NodeType == XmlNodeType.None) + xmlReader.Read(); + } + + /// + /// De-serialize an open stream into Environment. Does version/ compatibility management. + /// + /// Open for read stream + /// + public static AasCore.Aas3_0.Environment DeserializeXmlFromStreamWithCompat(Stream s) + { + // not sure + AasCore.Aas3_0.Environment res = null; + + // try get first element + var nsuri = TryReadXmlFirstElementNamespaceURI(s); + + // read V1.0? + if (nsuri != null && nsuri.Trim() == "http://www.admin-shell.io/aas/1/0") + { +#if !DoNotUseAasxCompatibilityModels + XmlSerializer serializer = new XmlSerializer( + typeof(AasxCompatibilityModels.AdminShellV10.AdministrationShellEnv), + "http://www.admin-shell.io/aas/1/0"); + var v10 = serializer.Deserialize(s) as AasxCompatibilityModels.AdminShellV10.AdministrationShellEnv; + res = new AasCore.Aas3_0.Environment(new List(), new List(), new List()); + res.ConvertFromV10(v10); + return res; +#else + throw (new Exception("Cannot handle AAS file format http://www.admin-shell.io/aas/1/0 !")); +#endif + } + + // read V2.0? + if (nsuri != null && nsuri.Trim() == "http://www.admin-shell.io/aas/2/0") + { +#if !DoNotUseAasxCompatibilityModels + XmlSerializer serializer = new XmlSerializer( + typeof(AasxCompatibilityModels.AdminShellV20.AdministrationShellEnv), + "http://www.admin-shell.io/aas/2/0"); + var v20 = serializer.Deserialize(s) as AasxCompatibilityModels.AdminShellV20.AdministrationShellEnv; + res = new AasCore.Aas3_0.Environment(new List(), new List(), new List()); + res.ConvertFromV20(v20); + return res; +#else + throw (new Exception("Cannot handle AAS file format http://www.admin-shell.io/aas/1/0 !")); +#endif + } + + // read V3.0? + if (nsuri != null && nsuri.Trim() == Xmlization.NS) + { + // dead-csharp off + //XmlSerializer serializer = new XmlSerializer( + // typeof(AasCore.Aas3_0_RC02.Environment), "http://www.admin-shell.io/aas/3/0"); + //res = serializer.Deserialize(s) as AasCore.Aas3_0_RC02.Environment; + // dead-csharp on + using (var xmlReader = XmlReader.Create(s)) + { + // TODO (MIHO, 2022-12-26): check if could be feature of AAS core + XmlSkipHeader(xmlReader); + res = Xmlization.Deserialize.EnvironmentFrom(xmlReader); + return res; + } + } + + // nope! + return null; + } + // dead-csharp off + //public static JsonSerializer BuildDefaultAasxJsonSerializer() + //{ + // var serializer = new JsonSerializer(); + // serializer.Converters.Add( + // new AdminShellConverters.JsonAasxConverter( + // "modelType", "name")); + // return serializer; + //} + public static T DeserializeFromJSON(string data) where T : IReferable + { + //using (var tr = new StringReader(data)) + //{ + //var serializer = BuildDefaultAasxJsonSerializer(); + //var rf = (T)serializer.Deserialize(tr, typeof(T)); + + var node = System.Text.Json.Nodes.JsonNode.Parse(data); + var rf = Jsonization.Deserialize.IReferableFrom(node); + + return (T)rf; + //} + } + + //public static T DeserializeFromJSON(JToken obj) where T : IReferable + //{ + // if (obj == null) + // return default(T); + // var serializer = BuildDefaultAasxJsonSerializer(); + // var rf = obj.ToObject(serializer); + // return rf; + //} + + ///// + ///// Use this, if DeserializeFromJSON is too tight. + ///// + //public static T DeserializePureObjectFromJSON(string data) + //{ + // using (var tr = new StringReader(data)) + // { + // //var serializer = BuildDefaultAasxJsonSerializer(); + // //var rf = (T)serializer.Deserialize(tr, typeof(T)); + // return null; + // } + //} + // dead-csharp on + } + + public abstract class AdminShellPackageEnvBase : IDisposable + { + public enum SerializationFormat { None, Xml, Json }; + + protected IEnvironment _aasEnv = new AasCore.Aas3_0.Environment(); + + public AdminShellPackageEnvBase() { } + + public AdminShellPackageEnvBase(AasCore.Aas3_0.Environment env) + { + if (env != null) + _aasEnv = env; + } + + public IEnvironment AasEnv + { + get + { + return _aasEnv; + } + } + + public void SetEnvironment(IEnvironment environment) + { + _aasEnv = environment; + } + + // TODO: remove, is not for base class!! + public virtual void SetFilename(string fileName) + { + } + + // TODO: remove, is not for base class!! + public virtual string Filename + { + get + { + return ""; + } + } + + protected static WebProxy proxy = null; + + public virtual Stream GetLocalStreamFromPackage( + string uriString, + string aasId = null, + string smId = null, + string idShortPath = null, + FileMode mode = FileMode.Open, + FileAccess access = FileAccess.Read) + { + // + // this part of the functionality works on HTTP and absolute files and is + // indepedent from a package storage. + // + + // split scheme and part + var sap = AdminShellUtil.GetSchemeAndPath(uriString); + if (sap == null) + return null; + + // Check, if remote + if (sap.Scheme.StartsWith("http")) + { + if (proxy == null) + { + string proxyAddress = ""; + string username = ""; + string password = ""; + + string proxyFile = "proxy.txt"; + if (System.IO.File.Exists(proxyFile)) + { + try + { // Open the text file using a stream reader. + using (StreamReader sr = new StreamReader(proxyFile)) + { + proxyFile = sr.ReadLine(); + } + } + catch (IOException e) + { + Console.WriteLine("proxy.txt could not be read:"); + Console.WriteLine(e.Message); + } + } + + try + { + using (StreamReader sr = new StreamReader(proxyFile)) + { + proxyAddress = sr.ReadLine(); + username = sr.ReadLine(); + password = sr.ReadLine(); + } + } + catch (Exception e) + { + Console.WriteLine(e.Message); + Console.WriteLine(proxyFile + " not found!"); + } + + if (proxyAddress != "") + { + proxy = new WebProxy(); + Uri newUri = new Uri(proxyAddress); + proxy.Address = newUri; + proxy.Credentials = new NetworkCredential(username, password); + Console.WriteLine("Using proxy: " + proxyAddress); + } + } + + var handler = new HttpClientHandler(); + + if (proxy != null) + handler.Proxy = proxy; + else + handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; + var hc = new HttpClient(handler); + + var response = hc.GetAsync(uriString).GetAwaiter().GetResult(); + + // if you call response.EnsureSuccessStatusCode here it will throw an exception + if (response.StatusCode == HttpStatusCode.Moved + || response.StatusCode == HttpStatusCode.Found) + { + var location = response.Headers.Location; + response = hc.GetAsync(location).GetAwaiter().GetResult(); + } + + response.EnsureSuccessStatusCode(); + var s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + + if (s.Length < 500) // indirect load? + { + StreamReader reader = new StreamReader(s); + string json = reader.ReadToEnd(); + var parsed = JObject.Parse(json); + try + { + string url = parsed.SelectToken("url").Value(); + response = hc.GetAsync(url).GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + } + + return s; + } + + // now, has to be file + if (sap.Scheme != "file") + return null; + + // if not starting with "/", if has to be an absolute file + if (!sap.Path.StartsWith('/')) + { + try + { + var stream = System.IO.File.Open(sap.Path, mode, access); + return stream; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + } + + return null; + } + + public virtual async Task GetLocalStreamFromPackageAsync( + string uriString, + string aasId = null, + string smId = null, + string idShortPath = null, + FileMode mode = FileMode.Open, + FileAccess access = FileAccess.Read) + { + await Task.Yield(); + return GetLocalStreamFromPackage(uriString, aasId, smId, idShortPath, mode, access); + } + + public virtual void PrepareSupplementaryFileParameters(ref string targetDir, ref string targetFn) + { + } + + public virtual string AddSupplementaryFileToStore( + string sourcePath, string targetDir, string targetFn, bool embedAsThumb, + AdminShellPackageSupplementaryFile.SourceGetByteChunk sourceGetBytesDel = null, string useMimeType = null) + { + return null; + } + + public virtual byte[] GetByteArrayFromUriOrLocalPackage(string uriString) + { + return null; + } + + /// + /// Ensures: + ///
  • result == null || result.CanRead
+ ///
+ public Stream GetLocalThumbnailStream() + { + Uri dummy = null; + var result = GetLocalThumbnailStream(ref dummy); + + // Post-condition + if (!(result == null || result.CanRead)) + { + throw new InvalidOperationException("Unexpected unreadable result stream"); + } + + return result; + } + + /// + /// Ensures: + ///
  • result == null || result.CanRead
+ ///
+ public virtual Stream GetLocalThumbnailStream(ref Uri thumbUri) + { + return null; + } + + public virtual Stream GetStreamFromUriOrLocalPackage(string uriString, + FileMode mode = FileMode.Open, + FileAccess access = FileAccess.Read) + { + return null; + } + + /// + /// This is intended to be the "new" one + /// + public virtual Stream GetThumbnailStreamFromAasOrPackage(string aasId) + { + return null; + } + + public virtual ListOfAasSupplementaryFile GetListOfSupplementaryFiles() + { + return null; + } + + public virtual void DeleteSupplementaryFile(AdminShellPackageSupplementaryFile psf) + { + } + + /// + /// Copies/ download contents and will return filename of temp file. + /// + /// + public virtual string MakePackageFileAvailableAsTempFile(string packageUri, bool keepFilename = false) + { + // this uses the virtual implementation and should therefore work ok in the base class + + // access + if (packageUri == null) + return null; + + // get input stream + using (var input = GetLocalStreamFromPackage(packageUri)) + { + // any + if (input == null) + return null; + + // generate tempfile name + string tempext = System.IO.Path.GetExtension(packageUri); + string temppath = System.IO.Path.GetTempFileName().Replace(".tmp", tempext); + + // maybe modify tempfile name? + if (keepFilename) + { + var masterFn = System.IO.Path.GetFileNameWithoutExtension(packageUri); + var tmpDir = System.IO.Path.GetDirectoryName(temppath); + var tmpFnExt = System.IO.Path.GetFileName(temppath); + + temppath = System.IO.Path.Combine(tmpDir, "" + masterFn + "_" + tmpFnExt); + } + + // copy to temp file + using (var temp = System.IO.File.OpenWrite(temppath)) + { + input.CopyTo(temp); + return temppath; + } + } + } + + /// + /// Copies/ download contents and will return filename of temp file. + /// + /// + public virtual async Task MakePackageFileAvailableAsTempFileAsync( + string packageUri, + string aasId = null, + string smId = null, + string idShortPath = null, + bool keepFilename = false) + { + // this uses the virtual implementation and should therefore work ok in the base class + + // access + if (packageUri == null) + return null; + + // get input stream + using (var input = await GetLocalStreamFromPackageAsync(packageUri, aasId, smId, idShortPath)) + { + // ok? + if (input == null) + return null; + + // generate tempfile name + string tempext = System.IO.Path.GetExtension(packageUri); + string temppath = System.IO.Path.GetTempFileName().Replace(".tmp", tempext); + + // maybe modify tempfile name? + if (keepFilename) + { + var masterFn = System.IO.Path.GetFileNameWithoutExtension(packageUri); + var tmpDir = System.IO.Path.GetDirectoryName(temppath); + var tmpFnExt = System.IO.Path.GetFileName(temppath); + + temppath = System.IO.Path.Combine(tmpDir, "" + masterFn + "_" + tmpFnExt); + } + + // copy to temp file + using (var temp = System.IO.File.OpenWrite(temppath)) + { + input.CopyTo(temp); + return temppath; + } + } + } + + public virtual bool SaveAs( + string fn, bool writeFreshly = false, + SerializationFormat prefFmt = SerializationFormat.None, + MemoryStream useMemoryStream = null, bool saveOnlyCopy = false) + { + return false; + } + + /// + /// Temporariyl saves & closes package and executes lambda. Afterwards, the package is re-opened + /// under the same file name + /// + /// Action which is to be executed while the file is CLOSED + /// Format for the saved file + public virtual void TemporarilySaveCloseAndReOpenPackage( + Action lambda, + AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None) + { + } + + public virtual bool IsOpen + { + get + { + // negative default behaviour + return false; + } + } + + public virtual bool IsLocalFile(string uriString) + { + return false; + } + + public virtual void Close() + { + } + + public virtual void Flush() + { + } + + public virtual void Dispose() + { + } + + // + // Binary file read + write + // + + public async Task GetByteArrayFromExternalInternalUri(string uri) + { + // split uri and access + var sap = AdminShellUtil.GetSchemeAndPath(uri); + if (sap == null) + return null; + + // directly a HTTP resource? + if (sap.Scheme.StartsWith("http")) + { + try + { + using (var client = new HttpClient()) + { + var ba = await client.GetByteArrayAsync(uri); + return ba; + } + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + return null; + } + } + + // no other schemes now + if (sap.Scheme != "file") + return null; + + // check if package local file + if (IsLocalFile(sap.Path)) + { + return GetByteArrayFromUriOrLocalPackage(sap.Path); + } + + // OK, assume a file accessible to this computer + try + { + var ba = await System.IO.File.ReadAllBytesAsync(sap.Path); + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + + // nope + return null; + } + + public async Task PutByteArrayToExternalUri(string uri, byte[] ba) + { + // split uri and access + var sap = AdminShellUtil.GetSchemeAndPath(uri); + if (ba == null || sap == null) + return false; + + // directly a HTTP resource? + if (sap.Scheme.StartsWith("http")) + { + try + { + using (var client = new HttpClient()) + using (var content = new ByteArrayContent(ba)) + { + content.Headers.ContentType = new MediaTypeHeaderValue("*/*"); + var response = await client.PostAsync(uri, content); + return response.IsSuccessStatusCode; + } + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + return false; + } + } + + // no other schemes now + if (sap.Scheme != "file") + return false; + + // just write + try + { + System.IO.File.WriteAllBytes(sap.Path, ba); + return true; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + + return false; + } + } + +} \ No newline at end of file diff --git a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs index 79dc2252f..cd56c5bbb 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs @@ -7,6 +7,7 @@ This source code is licensed under the Apache License 2.0 (see LICENSE.txt). This source code may use other Open Source software components (see LICENSE.txt). */ +using AdminShellNS.DiaryData; using Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -23,639 +24,10 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; +using Aas = AasCore.Aas3_0; namespace AdminShellNS { - /// - /// This class lets an outer functionality keep track on the supplementary files, which are in or - /// are pending to be added or deleted to an Package. - /// - public class AdminShellPackageSupplementaryFile /*: IReferable*/ - { - public delegate byte[] SourceGetByteChunk(); - - public enum LocationType { InPackage, AddPending, DeletePending } - - public enum SpecialHandlingType { None, EmbedAsThumbnail } - - public readonly Uri Uri = null; - - public readonly string UseMimeType = null; - - public readonly string SourceLocalPath = null; - public readonly SourceGetByteChunk SourceGetBytesDel = null; - - public LocationType Location; - public readonly SpecialHandlingType SpecialHandling; - - public AdminShellPackageSupplementaryFile( - Uri uri, string sourceLocalPath = null, LocationType location = LocationType.InPackage, - SpecialHandlingType specialHandling = SpecialHandlingType.None, - SourceGetByteChunk sourceGetBytesDel = null, string useMimeType = null) - { - Uri = uri; - UseMimeType = useMimeType; - SourceLocalPath = sourceLocalPath; - SourceGetBytesDel = sourceGetBytesDel; - Location = location; - SpecialHandling = specialHandling; - } - - // class derives from Referable in order to provide GetElementName - public string GetElementName() - { - return "File"; - } - - } - - public class ListOfAasSupplementaryFile : List - { - public AdminShellPackageSupplementaryFile FindByUri(string path) - { - if (path == null) - return null; - - return this.FirstOrDefault( - x => x?.Uri?.ToString().Trim() == path.Trim()); - } - } - - /// - /// Provides (static?) helpers for serializing AAS.. - /// - public static class AdminShellSerializationHelper - { - - public static string TryReadXmlFirstElementNamespaceURI(Stream s) - { - string res = null; - try - { - var xr = System.Xml.XmlReader.Create(s); - int i = 0; - while (xr.Read()) - { - // limit amount of read - i++; - if (i > 99) - // obviously not found - break; - - // find element - if (xr.NodeType == System.Xml.XmlNodeType.Element) - { - res = xr.NamespaceURI; - break; - } - } - xr.Close(); - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - } - - // return to zero pos - s.Seek(0, SeekOrigin.Begin); - - // give back - return res; - } - - /// - /// Skips first few tokens of an XML content until first "real" element is encountered - /// - /// - public static void XmlSkipHeader(XmlReader xmlReader) - { - while (xmlReader.NodeType == XmlNodeType.XmlDeclaration || - xmlReader.NodeType == XmlNodeType.Whitespace || - xmlReader.NodeType == XmlNodeType.Comment || - xmlReader.NodeType == XmlNodeType.None) - xmlReader.Read(); - } - - /// - /// De-serialize an open stream into Environment. Does version/ compatibility management. - /// - /// Open for read stream - /// - public static AasCore.Aas3_0.Environment DeserializeXmlFromStreamWithCompat(Stream s) - { - // not sure - AasCore.Aas3_0.Environment res = null; - - // try get first element - var nsuri = TryReadXmlFirstElementNamespaceURI(s); - - // read V1.0? - if (nsuri != null && nsuri.Trim() == "http://www.admin-shell.io/aas/1/0") - { -#if !DoNotUseAasxCompatibilityModels - XmlSerializer serializer = new XmlSerializer( - typeof(AasxCompatibilityModels.AdminShellV10.AdministrationShellEnv), - "http://www.admin-shell.io/aas/1/0"); - var v10 = serializer.Deserialize(s) as AasxCompatibilityModels.AdminShellV10.AdministrationShellEnv; - res = new AasCore.Aas3_0.Environment(new List(), new List(), new List()); - res.ConvertFromV10(v10); - return res; -#else - throw (new Exception("Cannot handle AAS file format http://www.admin-shell.io/aas/1/0 !")); -#endif - } - - // read V2.0? - if (nsuri != null && nsuri.Trim() == "http://www.admin-shell.io/aas/2/0") - { -#if !DoNotUseAasxCompatibilityModels - XmlSerializer serializer = new XmlSerializer( - typeof(AasxCompatibilityModels.AdminShellV20.AdministrationShellEnv), - "http://www.admin-shell.io/aas/2/0"); - var v20 = serializer.Deserialize(s) as AasxCompatibilityModels.AdminShellV20.AdministrationShellEnv; - res = new AasCore.Aas3_0.Environment(new List(), new List(), new List()); - res.ConvertFromV20(v20); - return res; -#else - throw (new Exception("Cannot handle AAS file format http://www.admin-shell.io/aas/1/0 !")); -#endif - } - - // read V3.0? - if (nsuri != null && nsuri.Trim() == Xmlization.NS) - { - // dead-csharp off - //XmlSerializer serializer = new XmlSerializer( - // typeof(AasCore.Aas3_0_RC02.Environment), "http://www.admin-shell.io/aas/3/0"); - //res = serializer.Deserialize(s) as AasCore.Aas3_0_RC02.Environment; - // dead-csharp on - using (var xmlReader = XmlReader.Create(s)) - { - // TODO (MIHO, 2022-12-26): check if could be feature of AAS core - XmlSkipHeader(xmlReader); - res = Xmlization.Deserialize.EnvironmentFrom(xmlReader); - return res; - } - } - - // nope! - return null; - } - // dead-csharp off - //public static JsonSerializer BuildDefaultAasxJsonSerializer() - //{ - // var serializer = new JsonSerializer(); - // serializer.Converters.Add( - // new AdminShellConverters.JsonAasxConverter( - // "modelType", "name")); - // return serializer; - //} - public static T DeserializeFromJSON(string data) where T : IReferable - { - //using (var tr = new StringReader(data)) - //{ - //var serializer = BuildDefaultAasxJsonSerializer(); - //var rf = (T)serializer.Deserialize(tr, typeof(T)); - - var node = System.Text.Json.Nodes.JsonNode.Parse(data); - var rf = Jsonization.Deserialize.IReferableFrom(node); - - return (T)rf; - //} - } - - //public static T DeserializeFromJSON(JToken obj) where T : IReferable - //{ - // if (obj == null) - // return default(T); - // var serializer = BuildDefaultAasxJsonSerializer(); - // var rf = obj.ToObject(serializer); - // return rf; - //} - - ///// - ///// Use this, if DeserializeFromJSON is too tight. - ///// - //public static T DeserializePureObjectFromJSON(string data) - //{ - // using (var tr = new StringReader(data)) - // { - // //var serializer = BuildDefaultAasxJsonSerializer(); - // //var rf = (T)serializer.Deserialize(tr, typeof(T)); - // return null; - // } - //} - // dead-csharp on - } - - public abstract class AdminShellPackageEnvBase : IDisposable - { - public enum SerializationFormat { None, Xml, Json }; - - protected IEnvironment _aasEnv = new AasCore.Aas3_0.Environment(); - - public AdminShellPackageEnvBase() { } - - public AdminShellPackageEnvBase(AasCore.Aas3_0.Environment env) - { - if (env != null) - _aasEnv = env; - } - - public IEnvironment AasEnv - { - get - { - return _aasEnv; - } - } - - public void SetEnvironment(IEnvironment environment) - { - _aasEnv = environment; - } - - // TODO: remove, is not for base class!! - public virtual void SetFilename(string fileName) - { - } - - // TODO: remove, is not for base class!! - public virtual string Filename - { - get - { - return ""; - } - } - - protected static WebProxy proxy = null; - - public virtual Stream GetLocalStreamFromPackage( - string uriString, - string aasId = null, - string smId = null, - string idShortPath = null, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) - { - // - // this part of the functionality works on HTTP and absolute files and is - // indepedent from a package storage. - // - - // split scheme and part - var sap = AdminShellUtil.GetSchemeAndPath(uriString); - if (sap == null) - return null; - - // Check, if remote - if (sap.Scheme.StartsWith("http")) - { - if (proxy == null) - { - string proxyAddress = ""; - string username = ""; - string password = ""; - - string proxyFile = "proxy.txt"; - if (System.IO.File.Exists(proxyFile)) - { - try - { // Open the text file using a stream reader. - using (StreamReader sr = new StreamReader(proxyFile)) - { - proxyFile = sr.ReadLine(); - } - } - catch (IOException e) - { - Console.WriteLine("proxy.txt could not be read:"); - Console.WriteLine(e.Message); - } - } - - try - { - using (StreamReader sr = new StreamReader(proxyFile)) - { - proxyAddress = sr.ReadLine(); - username = sr.ReadLine(); - password = sr.ReadLine(); - } - } - catch (Exception e) - { - Console.WriteLine(e.Message); - Console.WriteLine(proxyFile + " not found!"); - } - - if (proxyAddress != "") - { - proxy = new WebProxy(); - Uri newUri = new Uri(proxyAddress); - proxy.Address = newUri; - proxy.Credentials = new NetworkCredential(username, password); - Console.WriteLine("Using proxy: " + proxyAddress); - } - } - - var handler = new HttpClientHandler(); - - if (proxy != null) - handler.Proxy = proxy; - else - handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials; - var hc = new HttpClient(handler); - - var response = hc.GetAsync(uriString).GetAwaiter().GetResult(); - - // if you call response.EnsureSuccessStatusCode here it will throw an exception - if (response.StatusCode == HttpStatusCode.Moved - || response.StatusCode == HttpStatusCode.Found) - { - var location = response.Headers.Location; - response = hc.GetAsync(location).GetAwaiter().GetResult(); - } - - response.EnsureSuccessStatusCode(); - var s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); - - if (s.Length < 500) // indirect load? - { - StreamReader reader = new StreamReader(s); - string json = reader.ReadToEnd(); - var parsed = JObject.Parse(json); - try - { - string url = parsed.SelectToken("url").Value(); - response = hc.GetAsync(url).GetAwaiter().GetResult(); - response.EnsureSuccessStatusCode(); - s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - } - } - - return s; - } - - // now, has to be file - if (sap.Scheme != "file") - return null; - - // if not starting with "/", if has to be an absolute file - if (!sap.Path.StartsWith('/')) - { - try - { - var stream = System.IO.File.Open(sap.Path, mode, access); - return stream; - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - } - } - - return null; - } - - public virtual async Task GetLocalStreamFromPackageAsync( - string uriString, - string aasId = null, - string smId = null, - string idShortPath = null, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) - { - await Task.Yield(); - return GetLocalStreamFromPackage(uriString, aasId, smId, idShortPath, mode, access); - } - - public virtual void PrepareSupplementaryFileParameters(ref string targetDir, ref string targetFn) - { - } - - public virtual string AddSupplementaryFileToStore( - string sourcePath, string targetDir, string targetFn, bool embedAsThumb, - AdminShellPackageSupplementaryFile.SourceGetByteChunk sourceGetBytesDel = null, string useMimeType = null) - { - return null; - } - - public virtual byte[] GetByteArrayFromUriOrLocalPackage(string uriString) - { - return null; - } - - /// - /// Ensures: - ///
  • result == null || result.CanRead
- ///
- public Stream GetLocalThumbnailStream() - { - Uri dummy = null; - var result = GetLocalThumbnailStream(ref dummy); - - // Post-condition - if (!(result == null || result.CanRead)) - { - throw new InvalidOperationException("Unexpected unreadable result stream"); - } - - return result; - } - - /// - /// Ensures: - ///
  • result == null || result.CanRead
- ///
- public virtual Stream GetLocalThumbnailStream(ref Uri thumbUri) - { - return null; - } - - public virtual Stream GetStreamFromUriOrLocalPackage(string uriString, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) - { - return null; - } - - /// - /// This is intended to be the "new" one - /// - public virtual Stream GetThumbnailStreamFromAasOrPackage(string aasId) - { - return null; - } - - public virtual ListOfAasSupplementaryFile GetListOfSupplementaryFiles() - { - return null; - } - - public virtual void DeleteSupplementaryFile(AdminShellPackageSupplementaryFile psf) - { - } - - /// - /// Copies/ download contents and will return filenam of temp file. - /// - /// - public virtual string MakePackageFileAvailableAsTempFile(string packageUri, bool keepFilename = false) - { - return null; - } - - public virtual bool SaveAs( - string fn, bool writeFreshly = false, - SerializationFormat prefFmt = SerializationFormat.None, - MemoryStream useMemoryStream = null, bool saveOnlyCopy = false) - { - return false; - } - - /// - /// Temporariyl saves & closes package and executes lambda. Afterwards, the package is re-opened - /// under the same file name - /// - /// Action which is to be executed while the file is CLOSED - /// Format for the saved file - public virtual void TemporarilySaveCloseAndReOpenPackage( - Action lambda, - AdminShellPackageFileBasedEnv.SerializationFormat prefFmt = AdminShellPackageFileBasedEnv.SerializationFormat.None) - { - } - - public virtual bool IsOpen - { - get - { - // negative default behaviour - return false; - } - } - - public virtual bool IsLocalFile(string uriString) - { - return false; - } - - public virtual void Close() - { - } - - public virtual void Flush() - { - } - - public virtual void Dispose() - { - } - - // - // Binary file read + write - // - - public async Task GetByteArrayFromExternalInternalUri(string uri) - { - // split uri and access - var sap = AdminShellUtil.GetSchemeAndPath(uri); - if (sap == null) - return null; - - // directly a HTTP resource? - if (sap.Scheme.StartsWith("http")) - { - try - { - using (var client = new HttpClient()) - { - var ba = await client.GetByteArrayAsync(uri); - return ba; - } - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - return null; - } - } - - // no other schemes now - if (sap.Scheme != "file") - return null; - - // check if package local file - if (IsLocalFile(sap.Path)) - { - return GetByteArrayFromUriOrLocalPackage(sap.Path); - } - - // OK, assume a file accessible to this computer - try - { - var ba = await System.IO.File.ReadAllBytesAsync(sap.Path); - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - } - - // nope - return null; - } - - public async Task PutByteArrayToExternalUri(string uri, byte[] ba) - { - // split uri and access - var sap = AdminShellUtil.GetSchemeAndPath(uri); - if (ba == null || sap == null) - return false; - - // directly a HTTP resource? - if (sap.Scheme.StartsWith("http")) - { - try - { - using (var client = new HttpClient()) - using (var content = new ByteArrayContent(ba)) - { - content.Headers.ContentType = new MediaTypeHeaderValue("*/*"); - var response = await client.PostAsync(uri, content); - return response.IsSuccessStatusCode; - } - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - return false; - } - } - - // no other schemes now - if (sap.Scheme != "file") - return false; - - // just write - try - { - System.IO.File.WriteAllBytes(sap.Path, ba); - return true; - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - } - - return false; - } - } - /// /// This class encapsulates an AdminShellEnvironment and supplementary files into an AASX Package. /// Specifically has the capability to load, update and store .XML, .JSON and .AASX packages. @@ -1023,19 +395,30 @@ public void LoadFromAasEnvString(string content) } } - - // dead-csharp off - //public static XmlSerializerNamespaces GetXmlDefaultNamespaces() - //{ - // var nss = new XmlSerializerNamespaces(); - // nss.Add("xsi", System.Xml.Schema.XmlSchema.InstanceNamespace); - // nss.Add("aas", "http://www.admin-shell.io/aas/2/0"); - // nss.Add("IEC", "http://www.admin-shell.io/IEC61360/2/0"); - // nss.Add("abac", "http://www.admin-shell.io/aas/abac/2/0"); - // return nss; - //} - // dead-csharp on - + public void ClearTaintedFlag(Aas.IIdentifiable idf) + { + // access + if (idf is ITaintedData itd && itd.TaintedData?.Tainted != null) + itd.TaintedData.Tainted = null; + } + + public void ClearAllIdentifiableTaintedFlags() + { + // access + if (AasEnv == null) + return; + + // all + foreach (var aas in this.AasEnv.AllAssetAdministrationShells()) + ClearTaintedFlag(aas); + + foreach (var sm in this.AasEnv.AllSubmodels()) + ClearTaintedFlag(sm); + + foreach (var cd in this.AasEnv.AllConceptDescriptions()) + ClearTaintedFlag(cd); + } + public override bool SaveAs(string fn, bool writeFreshly = false, SerializationFormat prefFmt = SerializationFormat.None, MemoryStream useMemoryStream = null, bool saveOnlyCopy = false) { @@ -1072,6 +455,7 @@ public override bool SaveAs(string fn, bool writeFreshly = false, SerializationF writer.Flush(); writer.Close(); s.Flush(); + ClearAllIdentifiableTaintedFlags(); } finally { @@ -1110,6 +494,7 @@ public override bool SaveAs(string fn, bool writeFreshly = false, SerializationF Jsonization.Serialize.ToJsonObject(_aasEnv).WriteTo(wr); wr.Flush(); s.Flush(); + ClearAllIdentifiableTaintedFlags(); } finally { @@ -1545,6 +930,9 @@ public override bool SaveAs(string fn, bool writeFreshly = false, SerializationF string.Format("While write AASX {0} indirectly at {1} gave: {2}", fn, AdminShellUtil.ShortLocation(ex), ex.Message))); } + + // done + ClearAllIdentifiableTaintedFlags(); } catch (Exception ex) { @@ -2084,38 +1472,6 @@ public override void Dispose() Close(); } - public override string MakePackageFileAvailableAsTempFile(string packageUri, bool keepFilename = false) - { - // access - if (packageUri == null) - return null; - - // get input stream - using (var input = GetLocalStreamFromPackage(packageUri)) - { - // generate tempfile name - string tempext = System.IO.Path.GetExtension(packageUri); - string temppath = System.IO.Path.GetTempFileName().Replace(".tmp", tempext); - - // maybe modify tempfile name? - if (keepFilename) - { - var masterFn = System.IO.Path.GetFileNameWithoutExtension(packageUri); - var tmpDir = System.IO.Path.GetDirectoryName(temppath); - var tmpFnExt = System.IO.Path.GetFileName(temppath); - - temppath = System.IO.Path.Combine(tmpDir, "" + masterFn + "_" + tmpFnExt); - } - - // copy to temp file - using (var temp = System.IO.File.OpenWrite(temppath)) - { - input.CopyTo(temp); - return temppath; - } - } - } - public void EmbeddAssetInformationThumbnail(IResource defaultThumbnail, Stream fileContent) { // access diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index 8e9f967ad..d8670225f 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -821,7 +821,33 @@ public static IReferable FindReferableByReference( rootInfo.NrOfRootKeys = 1 + keyIndex; } - // give back the AAS + // test if to go further for Submodel + if (keyIndex < keyList.Count - 1 + && keyList[keyIndex + 1].Type == KeyTypes.Submodel) + { + var foundSm = environment.FindAllSubmodelGroupedByAAS((aas, sm) + => aas == keyedAas && sm.Id?.Trim() == keyList[keyIndex + 1].Value?.Trim()) + .FirstOrDefault(); + + if (foundSm != null) + { + keyIndex += 2; + + var foundSme = environment.FindReferableByReference(reference, keyIndex, + foundSm, foundSm.SubmodelElements); + + if (foundSme != null) + { + return foundSme; + } + else + { + return foundSm; + } + } + } + + // nope, give back the AAS return keyedAas; } @@ -879,9 +905,7 @@ public static IReferable FindReferableByReference( return environment.FindReferableByReference(reference, ++keyIndex, submodel, submodel.SubmodelElements); } - } - - + } if (firstKeyType.IsSME() && submodelElems != null) { diff --git a/src/AasxCsharpLibrary/Extensions/ExtendSubmodel.cs b/src/AasxCsharpLibrary/Extensions/ExtendSubmodel.cs index feafcf59f..bced6ce6b 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendSubmodel.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendSubmodel.cs @@ -372,7 +372,7 @@ public static IReference GetSemanticRef(this Submodel submodel) } } - public static List SmeForWrite(this Submodel submodel) + public static List SmeForWrite(this ISubmodel submodel) { if (submodel.SubmodelElements == null) submodel.SubmodelElements = new(); diff --git a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs index 3af02018f..f65846dd7 100644 --- a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs +++ b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs @@ -165,8 +165,9 @@ public static AnyUiBitmapInfo LoadBitmapInfoFromStream(Stream stream) // TODO (MIHO, 2023-02-23): make the whole thing async!! - public static AnyUiBitmapInfo MakePreviewFromPackageOrUrl( + public static async Task MakePreviewFromPackageOrUrlAsync( AdminShellPackageEnvBase package, string path, + string aasId, string smId, string idShortPath, double dpi = 75) { if (path == null) @@ -178,7 +179,7 @@ public static AnyUiBitmapInfo MakePreviewFromPackageOrUrl( { System.IO.Stream thumbStream = null; if (true /*= package?.IsLocalFile(path)*/) - thumbStream = package.GetLocalStreamFromPackage(path); + thumbStream = await package.GetLocalStreamFromPackageAsync(path, aasId, smId, idShortPath); else { // try download diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index b2d56b7fa..47708cd27 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -22,6 +22,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using Microsoft.Win32; using Newtonsoft.Json; using NPOI.HPSF; +using NPOI.HSSF.Record; using Org.BouncyCastle.Asn1.X509; using System; using System.Collections; @@ -42,6 +43,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Windows.Threading; using Workstation.ServiceModel.Ua; using static AasxPackageLogic.DispEditHelperBasics; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; using Aas = AasCore.Aas3_0; using ExhaustiveMatch = ExhaustiveMatching.ExhaustiveMatch; @@ -1726,6 +1728,33 @@ private void UiHandleReRenderAnyUiInEntityPanel( } } + private async Task UiSearchRepoAndExtendEnvironment( + AdminShellPackageEnvBase packEnv, + Aas.IReference workRef) + { + // check if env is dynamic fetch + if (packEnv is not AdminShellPackageDynamicFetchEnv dynPack) + return; + + //// try to find the last repo/ registry data + //DisplayElements.FindAllVisualElementTopToIdentifiable() + // .Where((ve) => ve is VisualElementEnvironmentItem veei + // && veei.theItemType == VisualElementEnvironmentItem.ItemType.Package + // && veei.the) + + // try get fetch record + var context = (dynPack.GetContext() as PackageContainerHttpRepoSubsetFetchContext); + var record = context?.Record as ConnectExtendedRecord; + if (record == null) + return; + + // make sure there is a base + if (record.BaseAddress?.HasContent() != true) + return; + + Task.Yield(); + } + private async Task UiHandleNavigateTo( Aas.IReference targetReference, bool alsoDereferenceObjects = true) @@ -1749,6 +1778,7 @@ private async Task UiHandleNavigateTo( this.DisplayElements.ExpandAllItems(); // incrementally make it unprecise + bool firstTime = true; while (work.Keys.Count > 0) { // try to find a business object in the package @@ -1775,6 +1805,14 @@ private async Task UiHandleNavigateTo( bo = boInfo?.BusinessObject; } + // TODO .. try search in connected repositories!!! + // Note: only now, after checking the "cheaper" alternatives + if (firstTime) + { + await UiSearchRepoAndExtendEnvironment(PackageCentral.Main, work); + firstTime = false; + } + // still yes? if (bo != null) { @@ -1839,6 +1877,8 @@ private async Task HandleApplicationEvent( if (evt is AasxIntegrationBase.AasxPluginResultEventNavigateToReference evtNavTo && evtNavTo.targetReference != null && evtNavTo.targetReference.Keys.Count > 0) { + Log.Singleton.Info("Plugin requested to naviagte to: " + evtNavTo.targetReference.ToStringExtended(1)); + await UiHandleNavigateTo(evtNavTo.targetReference); } diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index c3bf35e81..9bf83079e 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,7 +9,8 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); +// Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); +Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); // Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 0efb64761..7f1bc815a 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -69,7 +69,7 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\robotic_cell_for_demo_suitcase_new-v3.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02006-2-1_Template_Digital Nameplate_V3_Demo_ExportSMT - Kopie.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_play_02.aasx", - "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", + // "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\smt_pcn\\SMT_ProductChangeNotification_Draft_play_02.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\dnp30\\IDTA 02006-3-0_Template_Digital Nameplate_AsciiDoc_Draft_v06.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\contactv11\\IDTA 02002-1-1_Template_ContactInformation_AsciiDoc_Draft_v04.aasx", diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index c437dac9d..0463f0b06 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -497,7 +497,7 @@ public void DisplayOrEditEntityIdentifiable(AnyUiStackPanel stack, if (identifiable.Id.HasContent()) { this.AddKeyValue( - stack, "id (Base64)", AdminShellUtil.Base64Encode(identifiable.Id), + stack, "id (base64url)", AdminShellUtil.Base64UrlEncode(identifiable.Id), repo: null); } diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index f7e0f9210..0476a237a 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -174,6 +174,7 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( { // save await PackageCentral.MainItem.SaveAsAsync(runtimeOptions: PackageCentral.CentralRuntimeOptions); + // backup if (Options.Curr.BackupDir != null) diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index 31dee3115..c1b85c545 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -46,6 +46,7 @@ public class AdminShellPackageDynamicFetchEnv : AdminShellPackageEnvBase protected PackCntRuntimeOptions _runtimeOptions = null; protected Uri _defaultRepoBaseUri = null; + public Uri GetBaseUri() => _defaultRepoBaseUri; protected AdminShellPackageDynamicFetchContextBase _context = null; public AdminShellPackageDynamicFetchContextBase GetContext() => _context; diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 12bb2b2cc..fa51256ef 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -286,6 +286,33 @@ public static Uri CombineUri(Uri baseUri, string relativeUri) return new Uri(bu); } + // + // build query string + // + + public class LittleQueryString : List + { + public LittleQueryString Add(string key, string value) + { + Add(key + "=" + value); + return this; + } + + public LittleQueryString IfAdd(bool condition, string key, string value) + { + if (condition) + Add(key, value); + return this; + } + + public string ToQueryString() + { + if (Count < 1) + return ""; + return "?" + string.Join('&', this); + } + } + // // REPO // @@ -365,29 +392,35 @@ public static Uri BuildUriForRepoSingleSubmodel( bool encryptIds = true, bool usePost = false, bool addAasId = false, - string aasId = null) + string aasId = null, + bool levelDeep = true, + bool extentWith = true) { // access if (id?.HasContent() != true) return null; + // start query sttring + var qs = new LittleQueryString() + .IfAdd(levelDeep, "level", "deep") + .IfAdd(extentWith, "extent", "withBlobValue"); + // query string for aasId? - var queryAasId = ""; - if (addAasId && aasId?.HasContent() == true) + if (usePost && addAasId && aasId?.HasContent() == true) { var aasidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(aasId) : aasId; - queryAasId = "?aasIdentifier=" + aasidenc; + qs.Add("aasIdentifier", aasidenc); } // try combine if (!usePost) { var smidenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id; - return CombineUri(baseUri, $"submodels/{smidenc}"); + return CombineUri(baseUri, $"submodels/{smidenc}" + qs.ToQueryString()); } else { - return CombineUri(baseUri, "submodels" + queryAasId); + return CombineUri(baseUri, "submodels" + qs.ToQueryString()); } } diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 25b2a48e7..91267358d 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -1347,7 +1347,7 @@ public override void RefreshFromMainData() } } - public class VisualElementConceptDescription : VisualElementGeneric + public class VisualElementConceptDescription : VisualElementGeneric, ITaintableIdentifiable { public Aas.IEnvironment theEnv = null; public Aas.IConceptDescription theCD = null; @@ -1406,6 +1406,13 @@ public override object GetMainDataObject() return theCD; } + public DateTime? GetTaintedTime() + { + if (theCD is ITaintedData itd && itd.TaintedData?.Tainted != null) + return itd.TaintedData.Tainted; + return null; + } + public override void RefreshFromMainData() { if (theCD != null) diff --git a/src/AasxPluginDocumentShelf/DocumentEntity.cs b/src/AasxPluginDocumentShelf/DocumentEntity.cs index be7b72db2..518493104 100644 --- a/src/AasxPluginDocumentShelf/DocumentEntity.cs +++ b/src/AasxPluginDocumentShelf/DocumentEntity.cs @@ -27,7 +27,7 @@ namespace AasxPluginDocumentShelf public class DocumentEntity { public delegate void DocumentEntityEvent(DocumentEntity e); - public event DocumentEntityEvent DoubleClick = null; + public Func DoubleClick = null; public delegate Task MenuClickDelegate(DocumentEntity e, string menuItemHeader, object tag); public event MenuClickDelegate MenuClick = null; @@ -72,12 +72,19 @@ public class FileInfo public string Path = ""; public string MimeType = ""; + public string AasId = ""; + public string SmId = ""; + public string IdShortPath = ""; + public FileInfo() { } - public FileInfo(Aas.File file) + public FileInfo(string aasId, string smId, Aas.File file) { Path = file?.Value; MimeType = file?.ContentType; + AasId = aasId; + SmId = smId; + IdShortPath = file?.CollectIdShortByParent(separatorChar: '.', excludeIdentifiable: true); } } @@ -91,10 +98,10 @@ public DocumentEntity(string Title, string Organization, string FurtherInfo, str this.CountryCodes = LangCodes; } - public void RaiseDoubleClick() + public async Task RaiseDoubleClick() { if (DoubleClick != null) - DoubleClick(this); + await DoubleClick(this); } public async Task RaiseMenuClick(string menuItemHeader, object tag) @@ -174,7 +181,8 @@ public class ListOfDocumentEntity : List public static ListOfDocumentEntity ParseSubmodelForV10( AdminShellPackageEnvBase thePackage, - Aas.Submodel subModel, AasxPluginDocumentShelf.DocumentShelfOptions options, + Aas.IAssetAdministrationShell aas, + Aas.ISubmodel subModel, AasxPluginDocumentShelf.DocumentShelfOptions options, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) { @@ -301,7 +309,7 @@ public static ListOfDocumentEntity ParseSubmodelForV10( var fl = smcVer.Value.FindFirstSemanticIdAs( _semConfigV10.SemIdDigitalFile, MatchMode.Relaxed); - ent.DigitalFile = new DocumentEntity.FileInfo(fl); + ent.DigitalFile = new DocumentEntity.FileInfo("" + aas?.Id, "" + subModel?.Id, fl); // add ent.SmVersion = DocumentEntity.SubmodelVersion.Default; @@ -346,7 +354,9 @@ private static void SearchForRelations( public static ListOfDocumentEntity ParseSubmodelForV11( AdminShellPackageEnvBase thePackage, - Aas.Submodel subModel, AasxPredefinedConcepts.VDI2770v11 defs11, + Aas.IAssetAdministrationShell aas, + Aas.ISubmodel subModel, + AasxPredefinedConcepts.VDI2770v11 defs11, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) { @@ -506,12 +516,12 @@ public static ListOfDocumentEntity ParseSubmodelForV11( var fl = smcVer.Value.FindFirstSemanticIdAs( defs11.CD_DigitalFile?.GetReference(), MatchMode.Relaxed); if (fl != null) - ent.DigitalFile = new DocumentEntity.FileInfo(fl); + ent.DigitalFile = new DocumentEntity.FileInfo("" + aas?.Id, "" + subModel?.Id, fl); fl = smcVer.Value.FindFirstSemanticIdAs( defs11.CD_PreviewFile?.GetReference(), MatchMode.Relaxed); if (fl != null) - ent.PreviewFile = new DocumentEntity.FileInfo(fl); + ent.PreviewFile = new DocumentEntity.FileInfo("" + aas?.Id, "" + subModel?.Id, fl); // relations SearchForRelations(smcVer.Value, DocumentEntity.DocRelationType.DocumentedEntity, @@ -538,7 +548,7 @@ public static ListOfDocumentEntity ParseSubmodelForV11( semanticId: defs11.CD_PreviewFile?.GetReference(), value: path2); smcVer.Add(fl2); - ent.PreviewFile = new DocumentEntity.FileInfo(fl); + ent.PreviewFile = new DocumentEntity.FileInfo("" + aas?.Id, "" + subModel?.Id, fl); return true; }; } @@ -550,7 +560,9 @@ public static ListOfDocumentEntity ParseSubmodelForV11( public static ListOfDocumentEntity ParseSubmodelForV12( AdminShellPackageEnvBase thePackage, - Aas.Submodel subModel, AasxPredefinedConcepts.IdtaHandoverDocumentationV12 defs12, + Aas.IAssetAdministrationShell aas, + Aas.ISubmodel subModel, + AasxPredefinedConcepts.IdtaHandoverDocumentationV12 defs12, string defaultLang, int selectedDocClass, AasxLanguageHelper.LangEnum selectedLanguage) { @@ -711,12 +723,12 @@ public static ListOfDocumentEntity ParseSubmodelForV12( var fl = smcVer.Value.FindFirstSemanticIdAs( defs12.CD_DigitalFile?.GetReference(), MatchMode.Relaxed); if (fl != null) - ent.DigitalFile = new DocumentEntity.FileInfo(fl); + ent.DigitalFile = new DocumentEntity.FileInfo("" + aas?.Id, "" + subModel?.Id, fl); fl = smcVer.Value.FindFirstSemanticIdAs( defs12.CD_PreviewFile?.GetReference(), MatchMode.Relaxed); if (fl != null) - ent.PreviewFile = new DocumentEntity.FileInfo(fl); + ent.PreviewFile = new DocumentEntity.FileInfo("" + aas?.Id, "" + subModel?.Id, fl); // relations SearchForRelations(smcVer.Value, DocumentEntity.DocRelationType.DocumentedEntity, @@ -743,7 +755,7 @@ public static ListOfDocumentEntity ParseSubmodelForV12( semanticId: defs12.CD_PreviewFile?.GetReference(), value: path2); smcVer.Add(fl2); - ent.PreviewFile = new DocumentEntity.FileInfo(fl); + ent.PreviewFile = new DocumentEntity.FileInfo("" + aas?.Id, "" + subModel?.Id, fl); return true; }; } diff --git a/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs b/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs index 801958453..071fe8dbf 100644 --- a/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs +++ b/src/AasxPluginDocumentShelf/ShelfAnyUiControl.cs @@ -36,7 +36,8 @@ public class ShelfAnyUiControl private LogInstance _log = new LogInstance(); private AdminShellPackageEnvBase _package = null; - private Aas.Submodel _submodel = null; + private Aas.IAssetAdministrationShell _aas = null; + private Aas.ISubmodel _submodel = null; private DocumentShelfOptions _options = null; private PluginEventStack _eventStack = null; private PluginSessionBase _session = null; @@ -145,7 +146,7 @@ public void Dispose() public void Start( LogInstance log, AdminShellPackageEnvBase thePackage, - Aas.Submodel theSubmodel, + Aas.ISubmodel theSubmodel, DocumentShelfOptions theOptions, PluginEventStack eventStack, PluginSessionBase session, @@ -158,6 +159,7 @@ public void Start( _log = log; _package = thePackage; _submodel = theSubmodel; + _aas = _package?.AasEnv?.FindAasWithSubmodelId(_submodel?.Id); _options = theOptions; _eventStack = eventStack; _session = session; @@ -254,13 +256,13 @@ private void RenderFullShelf(AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk) // ReSharper disable ExpressionIsAlwaysNull if (_renderedVersion == DocumentEntity.SubmodelVersion.V12) _renderedEntities = ListOfDocumentEntity.ParseSubmodelForV12( - _package, _submodel, defs12, defaultLang, (int)_selectedDocClass, _selectedLang); + _package, _aas, _submodel, defs12, defaultLang, (int)_selectedDocClass, _selectedLang); else if (_renderedVersion == DocumentEntity.SubmodelVersion.V11) _renderedEntities = ListOfDocumentEntity.ParseSubmodelForV11( - _package, _submodel, defs11, defaultLang, (int)_selectedDocClass, _selectedLang); + _package, _aas, _submodel, defs11, defaultLang, (int)_selectedDocClass, _selectedLang); else _renderedEntities = ListOfDocumentEntity.ParseSubmodelForV10( - _package, _submodel, _options, defaultLang, (int)_selectedDocClass, _selectedLang); + _package, _aas, _submodel, _options, defaultLang, (int)_selectedDocClass, _selectedLang); // ReSharper enable ExpressionIsAlwaysNull // bring it to the panel @@ -517,7 +519,7 @@ protected void RenderPanelOutside( } // attach events and add - ent.DoubleClick += DocumentEntity_DoubleClick; + ent.DoubleClick = async (e) => await DocumentEntity_DoubleClick(e); ent.MenuClick += DocumentEntity_MenuClick; ent.DragStart += DocumentEntity_DragStart; } @@ -554,13 +556,13 @@ public AnyUiFrameworkElement RenderAnyUiDocumentEntity( // the border emits double clicks border.EmitEvent = AnyUiEventMask.LeftDouble; - border.setValueLambda = (o) => + border.setValueAsyncLambda = async (o) => { if (o is AnyUiEventData ed && ed.Mask == AnyUiEventMask.LeftDouble && ed.ClickCount == 2) { - de.RaiseDoubleClick(); + await de.RaiseDoubleClick(); } return new AnyUiLambdaActionNone(); }; @@ -1034,12 +1036,12 @@ private async Task DocumentEntity_MenuClick(DocumentEntity e, string menuItemHea // show digital file if (tag == null && menuItemHeader == "View file") - DocumentEntity_DisplaySaveFile(e, true, false); + await DocumentEntity_DisplaySaveFile(e, true, false); // save digital file? if (tag == null && menuItemHeader == "Save file .." && e.DigitalFile?.Path.HasContent() == true) { - DocumentEntity_DisplaySaveFile(e, true, true); + await DocumentEntity_DisplaySaveFile(e, true, true); } // show digital file @@ -1119,7 +1121,7 @@ private async Task DocumentEntity_MenuClick(DocumentEntity e, string menuItemHea } } - private void DocumentEntity_DisplaySaveFile(DocumentEntity e, bool display, bool save) + private async Task DocumentEntity_DisplaySaveFile(DocumentEntity e, bool display, bool save) { // first check if (e == null || e.DigitalFile?.Path == null || e.DigitalFile.Path.Trim().Length < 1 @@ -1134,7 +1136,8 @@ private void DocumentEntity_DisplaySaveFile(DocumentEntity e, bool display, bool { if (!inputFn.ToLower().Trim().StartsWith("http://") && !inputFn.ToLower().Trim().StartsWith("https://")) - inputFn = _package?.MakePackageFileAvailableAsTempFile(inputFn); + inputFn = await _package?.MakePackageFileAvailableAsTempFileAsync( + inputFn, e.DigitalFile.AasId, e.DigitalFile.SmId, e.DigitalFile.IdShortPath); } catch (Exception ex) { @@ -1157,9 +1160,9 @@ private void DocumentEntity_DisplaySaveFile(DocumentEntity e, bool display, bool } } - private void DocumentEntity_DoubleClick(DocumentEntity e) + private async Task DocumentEntity_DoubleClick(DocumentEntity e) { - DocumentEntity_DisplaySaveFile(e, true, false); + await DocumentEntity_DisplaySaveFile(e, true, false); } protected bool _inDragStart = false; @@ -1463,7 +1466,10 @@ private void DispatcherTimer_Tick(object sender, EventArgs e) lock (theDocEntitiesToPreview) { foreach (var de in theDocEntitiesToPreview) - _previewService.Push(new ShelfPreviewService.RenderEntity(_package, de?.DigitalFile?.Path)); + _previewService.Push(new ShelfPreviewService.RenderEntity( + _package, + de?.DigitalFile?.AasId, de?.DigitalFile?.SmId, de?.DigitalFile?.IdShortPath, + de?.DigitalFile?.Path)); theDocEntitiesToPreview.Clear(); } } diff --git a/src/AasxPluginDocumentShelf/ShelfPreviewService.cs b/src/AasxPluginDocumentShelf/ShelfPreviewService.cs index 06d32a836..c0e23268e 100644 --- a/src/AasxPluginDocumentShelf/ShelfPreviewService.cs +++ b/src/AasxPluginDocumentShelf/ShelfPreviewService.cs @@ -18,6 +18,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Linq; using System.Reflection; using System.Text; +using System.Threading.Tasks; namespace AasxPluginDocumentShelf { @@ -42,7 +43,7 @@ public void StartOperation(LogInstance log) _log = log; _dispatcherTimer = new System.Timers.Timer(_timerTickMs); - _dispatcherTimer.Elapsed += DispatcherTimer_Tick; + _dispatcherTimer.Elapsed += async (s,e) => await DispatcherTimer_Tick(s,e); _dispatcherTimer.Enabled = true; _dispatcherTimer.Start(); } @@ -61,15 +62,25 @@ public class RenderEntity public string PackageFn = null; public string SupplFn = null; + public string AasId = ""; + public string SmId = ""; + public string IdShortPath = ""; + public AnyUiBitmapInfo Bitmap = null; public AdminShellPackageEnvBase Package = null; public DateTime LastUse = DateTime.Now; - public RenderEntity(AdminShellPackageEnvBase package, string supplFn) + public RenderEntity( + AdminShellPackageEnvBase package, + string aasId, string smId, string idShortPart, + string supplFn) { Package = package; + AasId = aasId; + SmId = smId; + IdShortPath = idShortPart; PackageFn = package?.Filename; SupplFn = supplFn; } @@ -138,7 +149,7 @@ protected void PushRendered(RenderEntity ent) private bool _inDispatcherTimer = false; - private void DispatcherTimer_Tick(object sender, EventArgs e) + private async Task DispatcherTimer_Tick(object sender, EventArgs e) { // access if (_toRenderEntities == null || _renderedEntities == null || _inDispatcherTimer) @@ -169,7 +180,8 @@ private void DispatcherTimer_Tick(object sender, EventArgs e) if (ent?.Package != null && ent.PackageFn != null && ent.SupplFn != null) { // try check if Magick.NET library is available - var thumbBI = AnyUiGdiHelper.MakePreviewFromPackageOrUrl(ent.Package, ent.SupplFn); + var thumbBI = await AnyUiGdiHelper.MakePreviewFromPackageOrUrlAsync( + ent.Package, ent.SupplFn, ent.AasId, ent.SmId, ent.IdShortPath); if (thumbBI != null) { ent.Bitmap = thumbBI; diff --git a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs index 8c00b280a..1bc4a1486 100644 --- a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs +++ b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs @@ -388,16 +388,30 @@ protected void SetBasicInfos() MatchMode.Relaxed); if (fe?.Value != null) { + // build path var idShortPath = "" + fe.CollectIdShortByParent( separatorChar: '.', excludeIdentifiable: true); - var task = Task.Run(() => _package.GetLocalStreamFromPackageAsync( - uriString: fe.Value, - aasId: "" + aas?.Id, - smId: "" + _submodel.Id, - idShortPath: idShortPath)); + + // wrap async + var task = Task.Run(async () => + { + try + { + return await _package.GetLocalStreamFromPackageAsync( + uriString: fe.Value, + aasId: "" + aas?.Id, + smId: "" + _submodel.Id, + idShortPath: idShortPath); + } catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + return null; + }); task.Wait(); var stream = task.Result; + // convert to image bi = AnyUiGdiHelper.LoadBitmapInfoFromStream(stream); } diff --git a/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs b/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs index 601d47cb3..852dc0996 100644 --- a/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs +++ b/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs @@ -24,6 +24,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using Newtonsoft.Json; using WpfMtpControl; using WpfMtpControl.DataSources; +using System.IO.Packaging; namespace AasxPluginMtpViewer { @@ -31,29 +32,31 @@ public partial class WpfMtpControlWrapper : UserControl { // internal members - private LogInstance theLog = null; - private AdminShellPackageEnvBase thePackage = null; - private Aas.Submodel theSubmodel = null; - private AasxPluginMtpViewer.MtpViewerOptions theOptions = null; - private PluginEventStack theEventStack = null; + private LogInstance _log = null; + private AdminShellPackageEnvBase _package = null; + private Aas.IAssetAdministrationShell _aas = null; + private Aas.ISubmodel _submodel = null; + private AasxPluginMtpViewer.MtpViewerOptions _options = null; + private PluginEventStack _eventStack = null; - private AasxPredefinedConcepts.DefinitionsExperimental.InteropRelations defsInterop = null; - private DefinitionsMTP.ModuleTypePackage defsMtp = null; + private AasxPredefinedConcepts.DefinitionsExperimental.InteropRelations _defsInterop = null; + private DefinitionsMTP.ModuleTypePackage _defsMtp = null; - public MtpDataSourceOpcUaPreLoadInfo thePreLoadInfo = new MtpDataSourceOpcUaPreLoadInfo(); + private MtpDataSourceOpcUaPreLoadInfo _preLoadInfo = new MtpDataSourceOpcUaPreLoadInfo(); - private WpfMtpControl.MtpSymbolLib theSymbolLib = null; - private WpfMtpControl.MtpVisualObjectLib activeVisualObjectLib = null; - private WpfMtpControl.MtpData activeMtpData = null; + private WpfMtpControl.MtpSymbolLib _theSymbolLib = null; + private WpfMtpControl.MtpVisualObjectLib _activeVisualObjectLib = null; + private WpfMtpControl.MtpData _activeMtpData = null; - private Aas.File activeMtpFileElem = null; - private string activeMtpFileFn = null; + private Aas.ISubmodel _mtpTypeSm = null; + private Aas.File _activeMtpFileElem = null; + private string _activeMtpFileFn = null; - public WpfMtpControl.MtpVisuOpcUaClient client = new WpfMtpControl.MtpVisuOpcUaClient(); + public WpfMtpControl.MtpVisuOpcUaClient UaClient = new WpfMtpControl.MtpVisuOpcUaClient(); - private MtpDataSourceSubscriber activeSubscriber = null; + private MtpDataSourceSubscriber _activeSubscriber = null; - private MtpSymbolMapRecordList hintsForConfigRecs = null; + private MtpSymbolMapRecordList _hintsForConfigRecs = null; // window / plugin mechanics @@ -62,8 +65,8 @@ public WpfMtpControlWrapper() InitializeComponent(); // use pre-definitions - this.defsInterop = new AasxPredefinedConcepts.DefinitionsExperimental.InteropRelations(); - this.defsMtp = new DefinitionsMTP.ModuleTypePackage(); + this._defsInterop = new AasxPredefinedConcepts.DefinitionsExperimental.InteropRelations(); + this._defsMtp = new DefinitionsMTP.ModuleTypePackage(); } public void Start( @@ -73,11 +76,12 @@ public void Start( PluginEventStack eventStack, LogInstance log) { - this.thePackage = thePackage; - this.theSubmodel = theSubmodel; - this.theOptions = theOptions; - this.theEventStack = eventStack; - this.theLog = log; + _package = thePackage; + _submodel = theSubmodel; + _aas = _package?.AasEnv?.FindAasWithSubmodelId(_submodel?.Id); + _options = theOptions; + _eventStack = eventStack; + _log = log; } public static WpfMtpControlWrapper FillWithWpfControls( @@ -109,30 +113,46 @@ public static WpfMtpControlWrapper FillWithWpfControls( private void UserControl_Loaded(object sender, RoutedEventArgs e) { // initialize symbol library - this.theSymbolLib = new MtpSymbolLib(); + this._theSymbolLib = new MtpSymbolLib(); var ISO10628 = new ResourceDictionary(); ISO10628.Source = new Uri( "pack://application:,,,/WpfMtpControl;component/Resources/PNID_DIN_EN_ISO_10628.xaml"); - this.theSymbolLib.ImportResourceDicrectory("PNID_ISO10628", ISO10628); + this._theSymbolLib.ImportResourceDicrectory("PNID_ISO10628", ISO10628); var FESTO = new ResourceDictionary(); FESTO.Source = new Uri( "pack://application:,,,/WpfMtpControl;component/Resources/PNID_Festo.xaml"); - this.theSymbolLib.ImportResourceDicrectory("PNID_Festo", FESTO); + this._theSymbolLib.ImportResourceDicrectory("PNID_Festo", FESTO); // initialize visual object libraries - activeVisualObjectLib = new WpfMtpControl.MtpVisualObjectLib(); - activeVisualObjectLib.LoadStatic(this.theSymbolLib); + _activeVisualObjectLib = new WpfMtpControl.MtpVisualObjectLib(); + _activeVisualObjectLib.LoadStatic(this._theSymbolLib); // gather infos - var ok = GatherMtpInfos(this.thePreLoadInfo); - if (ok && this.activeMtpFileFn != null) + var ok = GatherMtpInfos(_preLoadInfo); + if (ok && _activeMtpFileFn != null && _mtpTypeSm != null) { // access file - var inputFn = this.activeMtpFileFn; + var inputFn = this._activeMtpFileFn; if (CheckIfPackageFile(inputFn)) - inputFn = thePackage.MakePackageFileAvailableAsTempFile(inputFn); + { + // build idShort Path + var idShortPath = "" + _activeMtpFileElem.CollectIdShortByParent( + separatorChar: '.', excludeIdentifiable: true); + + // _mtpTypeSm might be in another AAS + var mtpTypeAas = _package?.AasEnv?.FindAasWithSubmodelId(_mtpTypeSm.Id); + + // wrap async + var task = Task.Run(() => _package.MakePackageFileAvailableAsTempFileAsync( + packageUri: inputFn, + aasId: "" + mtpTypeAas?.Id, + smId: "" + _mtpTypeSm.Id, + idShortPath: idShortPath)); + task.Wait(); + inputFn = task.Result; + } // load file LoadFile(inputFn); @@ -154,12 +174,12 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e) private async void dispatcherTimer_Tick(object sender, EventArgs e) { - if (this.client == null) + if (this.UaClient == null) textBoxDataSourceStatus.Text = "(no OPC UA client enabled)"; else { - await client.TickAsync(100); - textBoxDataSourceStatus.Text = this.client.GetStatus(); + await UaClient.TickAsync(100); + textBoxDataSourceStatus.Text = this.UaClient.GetStatus(); } } @@ -168,23 +188,23 @@ private async void dispatcherTimer_Tick(object sender, EventArgs e) private bool GatherMtpInfos(MtpDataSourceOpcUaPreLoadInfo preLoadInfo) { // access - var env = this.thePackage?.AasEnv; - if (this.theSubmodel?.SemanticId == null || this.theSubmodel.SubmodelElements == null - || this.defsMtp == null + var env = _package?.AasEnv; + if (_submodel?.SemanticId == null || _submodel.SubmodelElements == null + || this._defsMtp == null || env?.AssetAdministrationShells == null - || this.thePackage.AasEnv.Submodels == null) + || _package.AasEnv.Submodels == null) return false; // need to find the type Submodel - Aas.ISubmodel mtpTypeSm = null; + _mtpTypeSm = null; // check, if the user pointed to the instance submodel - if (this.theSubmodel.SemanticId.Matches(this.defsMtp.SEM_MtpInstanceSubmodel)) + if (_submodel.SemanticId.Matches(this._defsMtp.SEM_MtpInstanceSubmodel)) { // Source list - foreach (var srcLst in this.theSubmodel.SubmodelElements + foreach (var srcLst in _submodel.SubmodelElements .FindAllSemanticIdAs( - this.defsMtp.CD_SourceList?.GetReference(), MatchMode.Relaxed)) + this._defsMtp.CD_SourceList?.GetReference(), MatchMode.Relaxed)) { // found a source list, might contain sources if (srcLst?.Value == null) @@ -192,12 +212,12 @@ private bool GatherMtpInfos(MtpDataSourceOpcUaPreLoadInfo preLoadInfo) // UA Server? foreach (var src in srcLst.Value.FindAllSemanticIdAs( - this.defsMtp.CD_SourceOpcUaServer?.GetReference(), MatchMode.Relaxed)) + this._defsMtp.CD_SourceOpcUaServer?.GetReference(), MatchMode.Relaxed)) if (src?.Value != null) { // UA server var ep = src.Value.FindFirstSemanticIdAs( - this.defsMtp.CD_Endpoint.GetReference(), MatchMode.Relaxed)?.Value; + this._defsMtp.CD_Endpoint.GetReference(), MatchMode.Relaxed)?.Value; // add if (preLoadInfo?.EndpointMapping != null) @@ -208,30 +228,30 @@ private bool GatherMtpInfos(MtpDataSourceOpcUaPreLoadInfo preLoadInfo) } // Identifier renaming? - foreach (var ren in theSubmodel.SubmodelElements + foreach (var ren in _submodel.SubmodelElements .FindAllSemanticIdAs( - this.defsMtp.CD_IdentifierRenaming?.GetReference(), MatchMode.Relaxed)) + this._defsMtp.CD_IdentifierRenaming?.GetReference(), MatchMode.Relaxed)) if (ren?.Value != null) { var oldtxt = ren.Value.FindFirstSemanticIdAs( - this.defsMtp.CD_RenamingOldText?.GetReference(), MatchMode.Relaxed)?.Value; + this._defsMtp.CD_RenamingOldText?.GetReference(), MatchMode.Relaxed)?.Value; var newtxt = ren.Value.FindFirstSemanticIdAs( - this.defsMtp.CD_RenamingNewText?.GetReference(), MatchMode.Relaxed)?.Value; + this._defsMtp.CD_RenamingNewText?.GetReference(), MatchMode.Relaxed)?.Value; if (oldtxt.HasContent() && newtxt.HasContent() && preLoadInfo?.IdentifierRenaming != null) preLoadInfo.IdentifierRenaming.Add(new MtpDataSourceStringReplacement(oldtxt, newtxt)); } // Namespace renaming? - foreach (var ren in theSubmodel.SubmodelElements + foreach (var ren in _submodel.SubmodelElements .FindAllSemanticIdAs( - this.defsMtp.CD_NamespaceRenaming?.GetReference(), MatchMode.Relaxed)) + this._defsMtp.CD_NamespaceRenaming?.GetReference(), MatchMode.Relaxed)) if (ren?.Value != null) { var oldtxt = ren?.Value.FindFirstSemanticIdAs( - this.defsMtp.CD_RenamingOldText?.GetReference(), MatchMode.Relaxed)?.Value; + this._defsMtp.CD_RenamingOldText?.GetReference(), MatchMode.Relaxed)?.Value; var newtxt = ren?.Value.FindFirstSemanticIdAs( - this.defsMtp.CD_RenamingNewText?.GetReference(), MatchMode.Relaxed)?.Value; + this._defsMtp.CD_RenamingNewText?.GetReference(), MatchMode.Relaxed)?.Value; if (oldtxt.HasContent() && newtxt.HasContent() && preLoadInfo?.NamespaceRenaming != null) preLoadInfo.NamespaceRenaming.Add(new MtpDataSourceStringReplacement(oldtxt, newtxt)); @@ -239,46 +259,46 @@ private bool GatherMtpInfos(MtpDataSourceOpcUaPreLoadInfo preLoadInfo) // according spec from Sten Gruener, the derivedFrom relationship shall be exploited. // How to get from subModel to AAS? - var instanceAas = env.FindAasWithSubmodelId(this.theSubmodel.Id); + var instanceAas = env.FindAasWithSubmodelId(_submodel.Id); var typeAas = env.FindReferableByReference(instanceAas?.DerivedFrom) as Aas.AssetAdministrationShell; if (instanceAas?.DerivedFrom != null && typeAas != null) foreach (var msm in env.FindAllSubmodelGroupedByAAS((aas, sm) => { - return aas == typeAas && true == sm?.SemanticId?.Matches(this.defsMtp.SEM_MtpSubmodel); + return aas == typeAas && true == sm?.SemanticId?.Matches(this._defsMtp.SEM_MtpSubmodel); })) { - mtpTypeSm = msm; + _mtpTypeSm = msm; break; } // another possibility: direct reference - var dirLink = this.theSubmodel.SubmodelElements + var dirLink = _submodel.SubmodelElements .FindFirstSemanticIdAs( - this.defsMtp.CD_MtpTypeSubmodel?.GetReference(), MatchMode.Relaxed); + this._defsMtp.CD_MtpTypeSubmodel?.GetReference(), MatchMode.Relaxed); var dirLinkSm = env.FindReferableByReference(dirLink?.Value) as Aas.Submodel; - if (mtpTypeSm == null) - mtpTypeSm = dirLinkSm; + if (_mtpTypeSm == null) + _mtpTypeSm = dirLinkSm; } // other (not intended) case: user points to type submodel directly - if (mtpTypeSm == null - && this.theSubmodel.SemanticId.Matches(this.defsMtp.SEM_MtpSubmodel)) - mtpTypeSm = this.theSubmodel; + if (_mtpTypeSm == null + && _submodel.SemanticId.Matches(this._defsMtp.SEM_MtpSubmodel)) + _mtpTypeSm = _submodel; // ok, is there a type submodel? - if (mtpTypeSm == null) + if (_mtpTypeSm == null) return false; // find file, remember Submodel element for it, find filename // (ConceptDescription)(no-local)[IRI]http://www.admin-shell.io/mtp/v1/MTPSUCLib/ModuleTypePackage - this.activeMtpFileElem = mtpTypeSm.SubmodelElements? - .FindFirstSemanticIdAs(this.defsMtp.CD_MtpFile.GetReference(), + this._activeMtpFileElem = _mtpTypeSm.SubmodelElements? + .FindFirstSemanticIdAs(this._defsMtp.CD_MtpFile.GetReference(), MatchMode.Relaxed); - var inputFn = this.activeMtpFileElem?.Value; + var inputFn = this._activeMtpFileElem?.Value; if (inputFn == null) return false; - this.activeMtpFileFn = inputFn; + this._activeMtpFileFn = inputFn; return true; } @@ -298,17 +318,17 @@ private void LoadFile(string fn) if (!".aml .zip .mtp".Contains(System.IO.Path.GetExtension(fn.Trim().ToLower()))) return; - this.client = new WpfMtpControl.MtpVisuOpcUaClient(); - this.client.ItemChanged += Client_ItemChanged; - this.activeSubscriber = new MtpDataSourceSubscriber(); - this.hintsForConfigRecs = new MtpSymbolMapRecordList(); + this.UaClient = new WpfMtpControl.MtpVisuOpcUaClient(); + this.UaClient.ItemChanged += Client_ItemChanged; + this._activeSubscriber = new MtpDataSourceSubscriber(); + this._hintsForConfigRecs = new MtpSymbolMapRecordList(); - this.activeMtpData = new WpfMtpControl.MtpData(); - this.activeMtpData.LoadAmlOrMtp(activeVisualObjectLib, - this.client, this.thePreLoadInfo, this.activeSubscriber, fn); + this._activeMtpData = new WpfMtpControl.MtpData(); + this._activeMtpData.LoadAmlOrMtp(_activeVisualObjectLib, + this.UaClient, this._preLoadInfo, this._activeSubscriber, fn); - if (this.activeMtpData.PictureCollection.Count > 0) - mtpVisu.SetPicture(this.activeMtpData.PictureCollection.Values.ElementAt(0)); + if (this._activeMtpData.PictureCollection.Count > 0) + mtpVisu.SetPicture(this._activeMtpData.PictureCollection.Values.ElementAt(0)); mtpVisu.RedrawMtp(); } @@ -316,20 +336,20 @@ private void Client_ItemChanged(WpfMtpControl.DataSources.IMtpDataSourceStatus d MtpVisuOpcUaClient.DetailItem itemRef, MtpVisuOpcUaClient.ItemChangeType changeType) { if (dataSource == null || itemRef == null || itemRef.MtpSourceItemId == null - || this.activeSubscriber == null) + || this._activeSubscriber == null) return; if (changeType == MtpVisuOpcUaClient.ItemChangeType.Value) - this.activeSubscriber.Invoke(itemRef.MtpSourceItemId, MtpDataSourceSubscriber.ChangeType.Value, + this._activeSubscriber.Invoke(itemRef.MtpSourceItemId, MtpDataSourceSubscriber.ChangeType.Value, itemRef.Value); } private void MtpVisu_MtpObjectDoubleClick(MtpData.MtpBaseObject source) { // access - var sme = this.theSubmodel?.SubmodelElements; - var first = this.activeMtpFileElem.GetReference(); - if (source == null || this.activeMtpFileElem == null || sme == null || first == null) + var sme = _submodel?.SubmodelElements; + var first = this._activeMtpFileElem.GetReference(); + if (source == null || this._activeMtpFileElem == null || sme == null || first == null) return; // for the active file, find a Reference for it @@ -344,16 +364,16 @@ private void MtpVisu_MtpObjectDoubleClick(MtpData.MtpBaseObject source) // var firstFtn = first.Add(new Aas.Key(Aas.KeyTypes.GlobalReference, searchId)); - this.theLog?.Info($"DblClick MTP .. search reference: {firstFtn.ToStringExtended(1)}"); + _log?.Info($"DblClick MTP .. search reference: {firstFtn.ToStringExtended(1)}"); foreach (var fileToNav in sme.FindAllSemanticIdAs( - this.defsInterop?.CD_FileToNavigateElement?.GetReference(), MatchMode.Relaxed)) + this._defsInterop?.CD_FileToNavigateElement?.GetReference(), MatchMode.Relaxed)) if (fileToNav.First?.Matches(firstFtn, MatchMode.Relaxed) == true) { // try activate var ev = new AasxIntegrationBase.AasxPluginResultEventNavigateToReference(); ev.targetReference = fileToNav.Second.Copy(); - this.theEventStack?.PushEvent(ev); + _eventStack?.PushEvent(ev); return; } @@ -362,17 +382,17 @@ private void MtpVisu_MtpObjectDoubleClick(MtpData.MtpBaseObject source) // var firstFte = first.Add(new Aas.Key(Aas.KeyTypes.GlobalReference, searchId)); - this.theLog?.Info($"DblClick MTP .. search reference: {firstFte.ToStringExtended(1)}"); + _log?.Info($"DblClick MTP .. search reference: {firstFte.ToStringExtended(1)}"); foreach (var fileToEnt in sme.FindAllSemanticIdAs( - this.defsInterop?.CD_FileToEntity?.GetReference(), MatchMode.Relaxed)) + this._defsInterop?.CD_FileToEntity?.GetReference(), MatchMode.Relaxed)) if (fileToEnt.First?.Matches(firstFte, MatchMode.Relaxed) == true) { // debug - this.theLog?.Info($"try find Entity {"" + fileToEnt.Second} .."); + _log?.Info($"try find Entity {"" + fileToEnt.Second} .."); // find Entity, check if self-contained - var foundRef = this.thePackage?.AasEnv?.FindReferableByReference(fileToEnt.Second); + var foundRef = _package?.AasEnv?.FindReferableByReference(fileToEnt.Second); if (foundRef is Aas.Entity foundEnt && foundEnt.EntityType == Aas.EntityType.SelfManagedEntity && foundEnt.GlobalAssetId != null) @@ -382,7 +402,7 @@ private void MtpVisu_MtpObjectDoubleClick(MtpData.MtpBaseObject source) ev.targetReference = new Aas.Reference(Aas.ReferenceTypes.ExternalReference, new Aas.IKey[] { new Aas.Key(Aas.KeyTypes.GlobalReference, foundEnt.GlobalAssetId) } .ToList()); - this.theEventStack?.PushEvent(ev); + _eventStack?.PushEvent(ev); return; } } @@ -401,7 +421,7 @@ private void SetOverlayPanelMode(int newMode) { case 2: this.ScrollViewerDataSources.Visibility = Visibility.Visible; - DataGridDataSources.ItemsSource = this.client.Items; + DataGridDataSources.ItemsSource = this.UaClient.Items; this.RichTextReport.Visibility = Visibility.Collapsed; break; @@ -465,14 +485,14 @@ private void ReportOnConfiguration(RichTextBox rtb) // Report on available library symbols // - if (this.theSymbolLib != null) + if (this._theSymbolLib != null) { AddToRichTextBox(rtb, "Library symbols", bold: true, fontSize: 18); AddToRichTextBox(rtb, "The following lists shows available symbol full names."); - foreach (var x in this.theSymbolLib.Values) + foreach (var x in this._theSymbolLib.Values) { AddToRichTextBox(rtb, "" + x.FullName, monoSpaced: true); } @@ -484,7 +504,7 @@ private void ReportOnConfiguration(RichTextBox rtb) // Hints for configurations // - if (this.hintsForConfigRecs != null) + if (this._hintsForConfigRecs != null) { AddToRichTextBox(rtb, "Preformatted configuration records", bold: true, fontSize: 18); AddToRichTextBox(rtb, @@ -495,7 +515,7 @@ private void ReportOnConfiguration(RichTextBox rtb) "For EClassVersions, 'null' disables version checking. " + "Either EClassClasses or EClassIRDIs shall be different to 'null'."); - foreach (var x in this.hintsForConfigRecs) + foreach (var x in this._hintsForConfigRecs) { var txt = JsonConvert.SerializeObject(x, Formatting.None); AddToRichTextBox(rtb, "" + txt, monoSpaced: true); diff --git a/src/AasxWpfControlLibrary/AnyUiWpf.cs b/src/AasxWpfControlLibrary/AnyUiWpf.cs index c81447bcb..cce73e8ef 100644 --- a/src/AasxWpfControlLibrary/AnyUiWpf.cs +++ b/src/AasxWpfControlLibrary/AnyUiWpf.cs @@ -319,7 +319,7 @@ private void InitRenderRecs() if ( ((cntl.EmitEvent & AnyUiEventMask.LeftDown) > 0) || ((cntl.EmitEvent & AnyUiEventMask.LeftDouble) > 0) ) { - wpf.MouseLeftButtonDown += (s5, e5) => { + wpf.MouseLeftButtonDown += async (s5, e5) => { if (e5.LeftButton == MouseButtonState.Pressed) { // get the current coordinates relative to the framework element @@ -338,6 +338,8 @@ private void InitRenderRecs() { var la = cntl.setValueLambda?.Invoke( new AnyUiEventData(AnyUiEventMask.LeftDown, cntl, e5.ClickCount, p)); + if (la == null && cntl.setValueAsyncLambda != null) + la = await cntl.setValueAsyncLambda.Invoke(cntl); EmitOutsideAction(la); } } @@ -699,15 +701,23 @@ private void InitRenderRecs() // double click if ((cntl.EmitEvent & AnyUiEventMask.MouseAll) > 0) { - wpf.MouseDown += (s2,e2) => + wpf.MouseDown += async (s2,e2) => { if (((cntl.EmitEvent & AnyUiEventMask.LeftDown) > 0) && (e2.ClickCount == 1)) - cntl.setValueLambda?.Invoke( + { + var la = cntl.setValueLambda?.Invoke( + new AnyUiEventData(AnyUiEventMask.LeftDouble, cntl, 2)); + if (la == null) await cntl.setValueAsyncLambda?.Invoke( new AnyUiEventData(AnyUiEventMask.LeftDouble, cntl, 2)); + } if (((cntl.EmitEvent & AnyUiEventMask.LeftDouble) > 0) && (e2.ClickCount == 2)) - cntl.setValueLambda?.Invoke( + { + var la = cntl.setValueLambda?.Invoke( + new AnyUiEventData(AnyUiEventMask.LeftDouble, cntl, 2)); + if (la == null) await cntl.setValueAsyncLambda?.Invoke( new AnyUiEventData(AnyUiEventMask.LeftDouble, cntl, 2)); + } }; } } From ef9bf93293f19e058c230e32217168e92866d3d3 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sat, 9 Nov 2024 17:32:40 +0100 Subject: [PATCH 29/99] * update before refactor local stream from package --- .../AdminShellPackageEnvBase.cs | 1 + src/AasxPackageExplorer/MainWindow.xaml.cs | 74 +++- .../DispEditHelperEntities.cs | 51 ++- .../PackageCentral/AasOnDemandEnvironment.cs | 1 - .../PackageCentral/OnDemandList.cs | 27 +- .../PackageContainerHttpRepoSubset.cs | 366 ++++++++++++------ 6 files changed, 384 insertions(+), 136 deletions(-) diff --git a/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs b/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs index fb91df6b0..31f35b632 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs @@ -420,6 +420,7 @@ public virtual Stream GetLocalStreamFromPackage( } } + // no, nothing more to find here (in base implementation!) return null; } diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 47708cd27..b451d26e8 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -30,6 +30,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.IO.Packaging; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -1728,13 +1729,19 @@ private void UiHandleReRenderAnyUiInEntityPanel( } } - private async Task UiSearchRepoAndExtendEnvironment( + private async Task UiSearchRepoAndExtendEnvironment( AdminShellPackageEnvBase packEnv, Aas.IReference workRef) { + Task.Yield(); + + // access + if (packEnv == null || workRef?.IsValid() != true) + return null; + // check if env is dynamic fetch if (packEnv is not AdminShellPackageDynamicFetchEnv dynPack) - return; + return null; //// try to find the last repo/ registry data //DisplayElements.FindAllVisualElementTopToIdentifiable() @@ -1742,17 +1749,66 @@ private async Task UiSearchRepoAndExtendEnvironment( // && veei.theItemType == VisualElementEnvironmentItem.ItemType.Package // && veei.the) - // try get fetch record + // try get a copy of the fetch record var context = (dynPack.GetContext() as PackageContainerHttpRepoSubsetFetchContext); - var record = context?.Record as ConnectExtendedRecord; + var record = (context?.Record as ConnectExtendedRecord)?.Copy(); if (record == null) - return; + return null; // make sure there is a base if (record.BaseAddress?.HasContent() != true) - return; + return null; - Task.Yield(); + // what to search? + string fullItemLocation = null; + + // search for AAS? + if (workRef.Count() >= 1 && workRef.Keys[0].Type == KeyTypes.AssetAdministrationShell) + { + // want to search for an AAS + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleAas); + record.AasId = workRef.Keys[0].Value; + fullItemLocation = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + } + + // search for Asset? + if (workRef.Count() >= 1 && workRef.Keys[0].Type == KeyTypes.GlobalReference) + { + // want to search for an Asset? + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AasByAssetLink); + record.AssetId = workRef.Keys[0].Value; + fullItemLocation = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + } + + // search any? + if (fullItemLocation != null) { + // try to load + // TODO: take over those options from existing container + var containerOptions = new PackageContainerHttpRepoSubset. + PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + record); + + var newIdfs = new List(); + var loadRes = await PackageContainerHttpRepoSubset.LoadFromSourceInternalAsync( + fullItemLocation: fullItemLocation, + targetEnv: packEnv, + loadNew: false, + trackNewIdentifiables: newIdfs, + containerOptions: containerOptions, + runtimeOptions: PackageCentral.CentralRuntimeOptions); + + if (loadRes == null || newIdfs.Count < 1) + return null; + + // rebuild display elements + DisplayElements.RebuildAasxElements( + PackageCentral, PackageCentral.Selector.Main, MainMenu?.IsChecked("EditMenu") == true, + lazyLoadingFirst: true); + + return newIdfs.FirstOrDefault(); + } + + return null; } private async Task UiHandleNavigateTo( @@ -1807,9 +1863,9 @@ private async Task UiHandleNavigateTo( // TODO .. try search in connected repositories!!! // Note: only now, after checking the "cheaper" alternatives - if (firstTime) + if (firstTime && bo == null) { - await UiSearchRepoAndExtendEnvironment(PackageCentral.Main, work); + bo = await UiSearchRepoAndExtendEnvironment(PackageCentral.Main, work); firstTime = false; } diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 05eefe3fd..69cf78bf9 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -1978,7 +1978,56 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( this.DisplayOrEditEntityIdentifiable( stack, env, aas, Options.Curr.TemplateIdAas, - null); + injectToId: new DispEditHelperModules.DispEditInjectAction( + new[] { "Rename" }, + (i) => + { + if (i == 0 && env != null) + { + var uc = new AnyUiDialogueDataTextBox( + "New ID:", + symbol: AnyUiMessageBoxImage.Question, + maxWidth: 1400, + text: aas.Id); + if (this.context.StartFlyoverModal(uc)) + { + var res = false; + + try + { + // rename + var lrf = env.RenameIdentifiable( + aas.Id, uc.Text); + + // use this information to emit events + if (lrf != null) + { + res = true; + foreach (var rf in lrf) + { + var rfi = rf.FindParentFirstIdentifiable(); + if (rfi != null) + this.AddDiaryEntry(rfi, new DiaryEntryStructChange()); + } + } + } + catch (Exception ex) + { + AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); + } + + if (!res) + this.context.MessageBoxFlyoutShow( + "The renaming of the AAS or some referring elements has not " + + "performed successfully! Please review your inputs and the AAS " + + "structure for any inconsistencies.", + "Warning", + AnyUiMessageBoxButton.OK, AnyUiMessageBoxImage.Warning); + return new AnyUiLambdaActionRedrawAllElements(aas); + } + } + return new AnyUiLambdaActionNone(); + })); // hasDataSpecification are MULTIPLE references. That is: multiple x multiple keys! this.DisplayOrEditEntityHasDataSpecificationReferences(stack, aas.EmbeddedDataSpecifications, diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index 7cb8c24b3..641120989 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -40,7 +40,6 @@ public class AasIdentifiableSideInfo : OnDemandSideInfoBase public bool IsStub = false; public AasIdentifiableSideInfoLevel StubLevel = AasIdentifiableSideInfoLevel.None; - public string Id = ""; public string IdShort = ""; public Uri Endpoint; diff --git a/src/AasxPackageLogic/PackageCentral/OnDemandList.cs b/src/AasxPackageLogic/PackageCentral/OnDemandList.cs index 35d43d68d..9374ef72a 100644 --- a/src/AasxPackageLogic/PackageCentral/OnDemandList.cs +++ b/src/AasxPackageLogic/PackageCentral/OnDemandList.cs @@ -31,15 +31,17 @@ namespace AasxPackageLogic.PackageCentral /// /// Base class for the side information describing the (status of the) data in /// the collection. Intended to be derived and stuffed with information. + /// Note: Id is required for the AddIfNew operation. /// public class OnDemandSideInfoBase { + public string Id = ""; } /// /// Pair of side information and actual data element. /// - public class OnDemandListItem + public class OnDemandListItem where T : Aas.IIdentifiable { public OnDemandSideInfoBase SideInfo; public T Data; @@ -48,7 +50,9 @@ public class OnDemandListItem /// /// Test /// - public class OnDemandList : IList where V : OnDemandSideInfoBase + public class OnDemandList : IList + where V : OnDemandSideInfoBase + where T : Aas.IIdentifiable { protected List> _items = new List>(); @@ -73,6 +77,16 @@ public void Add(T item, V sideInfo) _items.Add(new OnDemandListItem() { SideInfo = sideInfo, Data = item }); } + public bool AddIfNew(T item, V sideInfo) + { + var ndx = FindId(item?.Id); + if (ndx >= 0) + return false; + + _items.Add(new OnDemandListItem() { SideInfo = sideInfo, Data = item }); + return true; + } + void ICollection.Clear() { _items.Clear(); @@ -112,6 +126,15 @@ int IList.IndexOf(T item) return -1; } + public int FindId(string id) + { + for (int i = 0; i < _items.Count; i++) + if ((_items[i].Data?.Id?.HasContent() == true && _items[i].Data.Id == id) + || (_items[i].SideInfo?.Id?.HasContent() == true && _items[i].SideInfo.Id == id)) + return i; + return -1; + } + void IList.Insert(int index, T item) { _items.Insert(index, new OnDemandListItem() { Data = item }); diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index fa51256ef..365972b6c 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -37,6 +37,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.ComponentModel; using Lucene.Net.Util.Automaton; using RestSharp; +using static Lucene.Net.Search.FieldCache; namespace AasxPackageLogic.PackageCentral { @@ -218,7 +219,7 @@ public static bool IsValidUriForRegistrySingleAAS(string location) return m.Success; } - public static bool IsValidUriForRegistryAasByAssetId(string location) + public static bool IsValidUriForRepoRegistryAasByAssetId(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/lookup/shells/{0,1}\?(.*)assetId=([-A-Za-z0-9_]{1,99})", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); @@ -239,7 +240,7 @@ public static bool IsValidUriAnyMatch(string location) || IsValidUriForRepoQuery(location) || IsValidUriForRegistryAllAAS(location) || IsValidUriForRegistrySingleAAS(location) - || IsValidUriForRegistryAasByAssetId(location); + || IsValidUriForRepoRegistryAasByAssetId(location); } public static Uri GetBaseUri(string location) @@ -577,13 +578,14 @@ public static bool HasProperty(dynamic obj, string name) return objType.GetProperty(name) != null; } - private async Task FromRegistryGetAasAndSubmodels( + private static async Task FromRegistryGetAasAndSubmodels( OnDemandListIdentifiable prepAas, OnDemandListIdentifiable prepSM, - ConnectExtendedRecord record, + ConnectExtendedRecord record, PackCntRuntimeOptions runtimeOptions, bool allowFakeResponses, - dynamic aasDescriptor) + dynamic aasDescriptor, + List trackNewIdentifiables = null) { // access if (record == null) @@ -673,7 +675,8 @@ private async Task FromRegistryGetAasAndSubmodels( (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, smrr.Id) }).ToList())); // add this AAS - prepAas?.Add(aas, aasSi); + if (prepAas?.AddIfNew(aas, aasSi) == true) + trackNewIdentifiables?.Add(aas); // check if to add the Submodels if (!record.AutoLoadOnDemand) @@ -696,12 +699,13 @@ private async Task FromRegistryGetAasAndSubmodels( // add as pure side info si.IsStub = true; - prepSM.Add(null, si); + prepSM?.AddIfNew(null, si); } // no, add with data si.IsStub = false; - prepSM?.Add(sm, si); + if (prepSM?.AddIfNew(sm, si) == true) + trackNewIdentifiables?.Add(sm); }); } else @@ -720,7 +724,7 @@ private async Task FromRegistryGetAasAndSubmodels( // need to do si.IsStub = true; - prepSM.Add(null, si); + prepSM?.AddIfNew(null, si); } } @@ -737,6 +741,23 @@ public override async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) + { + var newEnv = await LoadFromSourceInternalAsync( + fullItemLocation, + Env, + containerOptions: containerOptions, runtimeOptions: runtimeOptions); + + if (newEnv != null) + Env = newEnv; + } + + public static async Task LoadFromSourceInternalAsync( + string fullItemLocation, + AdminShellPackageEnvBase targetEnv = null, + bool loadNew = true, + List trackNewIdentifiables = null, + PackageContainerOptionsBase containerOptions = null, + PackCntRuntimeOptions runtimeOptions = null) { var allowFakeResponses = runtimeOptions?.AllowFakeResponses ?? false; @@ -744,66 +765,59 @@ public override async Task LoadFromSourceAsync( var baseUri = GetBaseUri(fullItemLocation); - // for the time being, make sure, we have the correct list implementations - var prepAas = new OnDemandListIdentifiable(); - var prepSM = new OnDemandListIdentifiable(); - var prepCD = new OnDemandListIdentifiable(); + // use existing ? + var env = (targetEnv as AdminShellPackageEnvBase)?.AasEnv; + var dynPack = targetEnv as AdminShellPackageDynamicFetchEnv; - // integrate in a fresh environment - // TODO: new kind of environment - var env = (Aas.IEnvironment) new AasOnDemandEnvironment(); + var prepAas = env?.AssetAdministrationShells as OnDemandListIdentifiable + ?? new OnDemandListIdentifiable(); + var prepSM = env?.Submodels as OnDemandListIdentifiable + ?? new OnDemandListIdentifiable(); + var prepCD = env?.ConceptDescriptions as OnDemandListIdentifiable + ?? new OnDemandListIdentifiable(); - // already set structure to use some convenience functions - env.AssetAdministrationShells = prepAas; - env.Submodels = prepSM; - env.ConceptDescriptions = prepCD; + if (!loadNew && (env == null || dynPack == null)) + return null; - // also the package "around" - var dynPack = new AdminShellPackageDynamicFetchEnv(runtimeOptions, baseUri); + // new + if (loadNew) + { + // for the time being, make sure, we have the correct list implementations + prepAas = new OnDemandListIdentifiable(); + prepSM = new OnDemandListIdentifiable(); + prepCD = new OnDemandListIdentifiable(); + + // integrate in a fresh environment + // TODO: new kind of environment + env = (Aas.IEnvironment) new AasOnDemandEnvironment(); + + // already set structure to use some convenience functions + env.AssetAdministrationShells = prepAas; + env.Submodels = prepSM; + env.ConceptDescriptions = prepCD; + + // also the package "around" + dynPack = new AdminShellPackageDynamicFetchEnv(runtimeOptions, baseUri); + } - // get the record data + // get the record data (as supplemental infos to the fullItemLocation) var record = (containerOptions as PackageContainerHttpRepoSubsetOptions)?.Record; - // invalidate cursor data + // invalidate cursor data (as a new request is about to be started) string cursor = null; // TODO: very long function, needs to be refactored + var operationFound = false; // - // REGISTRY + // in REPO & REGISTRY // - var operationFound = false; - - if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository + || record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) { - // All AAS descriptors? - if (IsValidUriForRegistryAllAAS(fullItemLocation)) - { - // ok - operationFound = true; - - // prepare receiving the descriptors - var resObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( - new Uri(fullItemLocation), runtimeOptions, allowFakeResponses); - - // have directly a list of descriptors?! - if (resObj?.result == null) - { - runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); - return; - } - - foreach (var res in resObj.result) - { - // refer to dedicated function - await FromRegistryGetAasAndSubmodels( - prepAas, prepSM, record, runtimeOptions, allowFakeResponses, res); - } - } - // Asset Link - if (IsValidUriForRegistryAasByAssetId(fullItemLocation)) + if (IsValidUriForRepoRegistryAasByAssetId(fullItemLocation)) { // ok operationFound = true; @@ -816,7 +830,7 @@ await FromRegistryGetAasAndSubmodels( if (resObj == null) { runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); - return; + return null; } // Have a list of ids. Decompose into single id. @@ -826,29 +840,92 @@ await FromRegistryGetAasAndSubmodels( { noRes = false; - // in res, have only an id. Get the descriptor + // in res, have only an id. Get the descriptor / the AAS itself var id = "" + res; - var singleDesc = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( - BuildUriForRegistrySingleAAS(baseUri, id, encryptIds: true), + + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) + { + var singleDesc = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( + BuildUriForRegistrySingleAAS(baseUri, id, encryptIds: true), + runtimeOptions, allowFakeResponses); + if (singleDesc == null || !HasProperty(singleDesc, "endpoints")) + continue; + + // refer to dedicated function + await FromRegistryGetAasAndSubmodels( + prepAas, prepSM, record, runtimeOptions, allowFakeResponses, singleDesc, + trackNewIdentifiables); + } + + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + // get the AAS (new download approach) + var aas = await PackageHttpDownloadUtil.DownloadIdentifiableToOK( + BuildUriForRepoSingleAAS(baseUri, id, encryptIds: true), runtimeOptions, allowFakeResponses); - if (singleDesc == null || !HasProperty(singleDesc, "endpoints")) - continue; - // refer to dedicated function - await FromRegistryGetAasAndSubmodels( - prepAas, prepSM, record, runtimeOptions, allowFakeResponses, singleDesc); + // foudn? + if (aas != null) + { + // add + if (prepAas.AddIfNew(aas, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = aas.Id, + IdShort = aas.IdShort, + Endpoint = new Uri(fullItemLocation) + })) + { + trackNewIdentifiables?.Add(aas); + } + } + } } // check again (count) if (noRes) { runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); - return; + return null; + } + } + } + + // + // REGISTRY + // + + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Registry) + { + // All AAS descriptors? + if (!operationFound && IsValidUriForRegistryAllAAS(fullItemLocation)) + { + // ok + operationFound = true; + + // prepare receiving the descriptors + var resObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( + new Uri(fullItemLocation), runtimeOptions, allowFakeResponses); + + // have directly a list of descriptors?! + if (resObj?.result == null) + { + runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + return null; + } + + foreach (var res in resObj.result) + { + // refer to dedicated function + await FromRegistryGetAasAndSubmodels( + prepAas, prepSM, record, runtimeOptions, allowFakeResponses, res, + trackNewIdentifiables); } } // start with single AAS? - if (IsValidUriForRegistrySingleAAS(fullItemLocation)) + if (!operationFound && IsValidUriForRegistrySingleAAS(fullItemLocation)) { // ok operationFound = true; @@ -887,16 +964,17 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( if (aasDesc?.id == null) { runtimeOptions?.Log?.Error("Registry did not return a valid AAS descriptor! Aborting."); - return; + return null; } // refer to dedicated function var res = await FromRegistryGetAasAndSubmodels( - prepAas, prepSM, record, runtimeOptions, allowFakeResponses, aasDesc); + prepAas, prepSM, record, runtimeOptions, allowFakeResponses, aasDesc, + trackNewIdentifiables); if (!res) { runtimeOptions?.Log?.Error("Error retrieving AAS from registry! Aborting."); - return; + return null; } } } @@ -913,7 +991,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // start with a list of AAS or Submodels (very similar) var isAllAAS = IsValidUriForRepoAllAAS(fullItemLocation); var isAllSM = IsValidUriForRepoAllSubmodel(fullItemLocation); - if (isAllAAS || isAllSM) + if (!operationFound && (isAllAAS || isAllSM)) { // ok operationFound = true; @@ -975,14 +1053,17 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( firstNonSkipped = false; // add + var added = false; if (isAllAAS) - prepAas.Add( + added = prepAas.AddIfNew( idf as Aas.IAssetAdministrationShell, si); if (isAllSM) - prepSM.Add( + added = prepSM.AddIfNew( idf as Aas.ISubmodel, si); + if (added) + trackNewIdentifiables?.Add(idf); } catch (Exception ex) { @@ -1005,7 +1086,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } // start with single AAS? - if (IsValidUriForRepoSingleAAS(fullItemLocation)) + if (!operationFound && IsValidUriForRepoSingleAAS(fullItemLocation)) { // ok operationFound = true; @@ -1024,13 +1105,17 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var aas = Jsonization.Deserialize.AssetAdministrationShellFrom(node); - prepAas.Add(aas, new AasIdentifiableSideInfo() { + if (prepAas.AddIfNew(aas, new AasIdentifiableSideInfo() + { IsStub = false, StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, Id = aas.Id, IdShort = aas.IdShort, Endpoint = new Uri(fullItemLocation) - }); + })) + { + trackNewIdentifiables?.Add(aas); + } } catch (Exception ex) { @@ -1040,7 +1125,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } // start with Submodel? - if (IsValidUriForRepoSingleSubmodel(fullItemLocation)) + if (!operationFound && IsValidUriForRepoSingleSubmodel(fullItemLocation)) { // ok operationFound = true; @@ -1059,14 +1144,17 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var sm = Jsonization.Deserialize.SubmodelFrom(node); - prepSM.Add(sm, new AasIdentifiableSideInfo() + if (prepSM.AddIfNew(sm, new AasIdentifiableSideInfo() { IsStub = false, StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, Id = sm.Id, IdShort = sm.IdShort, Endpoint = new Uri(fullItemLocation) - }); + })) + { + trackNewIdentifiables?.Add(sm); + } } catch (Exception ex) { @@ -1076,7 +1164,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } // start with CD? - if (IsValidUriForRepoSingleCD(fullItemLocation)) + if (!operationFound && IsValidUriForRepoSingleCD(fullItemLocation)) { // ok operationFound = true; @@ -1095,14 +1183,17 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var cd = Jsonization.Deserialize.ConceptDescriptionFrom(node); - prepCD.Add(cd, new AasIdentifiableSideInfo() + if (prepCD.AddIfNew(cd, new AasIdentifiableSideInfo() { IsStub = false, StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, Id = cd.Id, IdShort = cd.IdShort, Endpoint = new Uri(fullItemLocation) - }); + })) + { + trackNewIdentifiables?.Add(cd); + } } catch (Exception ex) { @@ -1112,7 +1203,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } // start with a query? - if (IsValidUriForRepoQuery(fullItemLocation)) + if (!operationFound && IsValidUriForRepoQuery(fullItemLocation)) { // ok operationFound = true; @@ -1136,7 +1227,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( if (!query.HasContent()) { runtimeOptions?.Log?.Error("Could not determine valid query script. Aborting!"); - return; + return null; } // but, the query needs to be reformatted as JSON @@ -1189,7 +1280,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { Log.Singleton.Error("Could not fetch new dynamic elements by graphql. Aborting!"); Log.Singleton.Error(" POST request was: {0}", jsonQuery); - return; + return null; } // only makes sense, if query returns something @@ -1197,7 +1288,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { Log.Singleton.Info(StoredPrint.Color.Blue, "Query resulted in zero elements, " + "which could be fetched. Aborting!"); - return; + return null; } // skip items? @@ -1244,14 +1335,17 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var sm = Jsonization.Deserialize.SubmodelFrom(node); if (fi.Type == FetchItemType.SmUrl || fi.Type == FetchItemType.SmId) - prepSM.Add(sm, new AasIdentifiableSideInfo() + if (prepSM.AddIfNew(sm, new AasIdentifiableSideInfo() { IsStub = false, StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, Id = sm.Id, IdShort = sm.IdShort, Endpoint = new Uri(fullItemLocation) - }); + })) + { + trackNewIdentifiables?.Add(sm); + } } catch (Exception ex) { @@ -1272,7 +1366,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } // start auto-load missing Submodels? - if (record?.AutoLoadSubmodels ?? false) + if (operationFound && (record?.AutoLoadSubmodels ?? false)) { var lrs = env.FindAllSubmodelReferences(onlyNotExisting: true).ToList(); @@ -1285,7 +1379,7 @@ await Parallel.ForEachAsync(lrs, // side info level 1 lock (prepSM) { - prepSM.Add(null, new AasIdentifiableSideInfo() + prepSM.AddIfNew(null, new AasIdentifiableSideInfo() { IsStub = true, StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, @@ -1315,14 +1409,17 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var sm = Jsonization.Deserialize.SubmodelFrom(node); lock (prepSM) { - prepSM.Add(sm, new AasIdentifiableSideInfo() + if (prepSM.AddIfNew(sm, new AasIdentifiableSideInfo() { IsStub = false, StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, Id = sm.Id, IdShort = sm.IdShort, Endpoint = sourceUri - }); + })) + { + trackNewIdentifiables?.Add(sm); + } } } catch (Exception ex) @@ -1335,7 +1432,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } // start auto-load missing thumbnails? - if (record?.AutoLoadThumbnails ?? false) + if (operationFound && (record?.AutoLoadThumbnails ?? false)) await Parallel.ForEachAsync(env.AllAssetAdministrationShells(), new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, async (aas, token) => @@ -1373,9 +1470,14 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( runtimeOptions?.Log?.Error("Did not found any matching operation in location to " + "execute on Registry or Repository! Location was: {0}", fullItemLocation); - return; + return null; } + // bring back to env + env.AssetAdministrationShells = prepAas; + env.Submodels = prepSM; + env.ConceptDescriptions = prepCD; + // remove, what is not need if (env.AssetAdministrationShellCount() < 1) env.AssetAdministrationShells = null; @@ -1385,13 +1487,15 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( env.ConceptDescriptions = null; // commit - Env = dynPack; - Env.SetEnvironment(env); - EnvDynPack?.SetContext(new PackageContainerHttpRepoSubsetFetchContext() + targetEnv = dynPack; + targetEnv.SetEnvironment(env); + (targetEnv as AdminShellPackageDynamicFetchEnv)?.SetContext(new PackageContainerHttpRepoSubsetFetchContext() { Record = record, Cursor = cursor }); + + return targetEnv; } public override async Task SaveLocalCopyAsync( @@ -1532,15 +1636,26 @@ public enum BaseTypeEnum { Repository, Registry } [JsonIgnore] public int PageOffset; - public void SetQueryChoices(int choice) + public enum QueryChoice { + None, + AllAas, + SingleAas, + AasByAssetLink, + AllSM, + SingleSM, + SingleCD, + Query + } + + public void SetQueryChoices(QueryChoice choice) { - GetAllAas = (choice == 1); - GetSingleAas = (choice == 2); - GetAasByAssetLink = (choice == 3); - GetAllSubmodel = (choice == 4); - GetSingleSubmodel = (choice == 5); - GetSingleCD = (choice == 6); - ExecuteQuery = (choice == 7); + GetAllAas = (choice == QueryChoice.AllAas); + GetSingleAas = (choice == QueryChoice.SingleAas); + GetAasByAssetLink = (choice == QueryChoice.AasByAssetLink); + GetAllSubmodel = (choice == QueryChoice.AllSM); + GetSingleSubmodel = (choice == QueryChoice.SingleSM); + GetSingleCD = (choice == QueryChoice.SingleCD); + ExecuteQuery = (choice == QueryChoice.Query); } public string GetBaseTypStr() @@ -1566,6 +1681,7 @@ public string GetFetchOperationStr() { if (GetAllAas) res = "GetAllAssetAdministrationShells"; if (GetSingleAas) res = "GetAssetAdministrationShellById"; + if (GetAasByAssetLink) res = "GetAllAssetAdministrationShellIdsByAssetLink"; if (GetAllSubmodel) res = "GetAllSubmodels"; if (GetSingleSubmodel) res = "GetSubmodelById"; if (GetSingleCD) res = "GetConceptDescriptionById"; @@ -1616,14 +1732,21 @@ public static string BuildLocationFrom( { // if a skip has been requested, these AAS need to be loaded, as well var uri = BuildUriForRepoAllAAS(baseUri, record.PageLimit + record.PageSkip, cursor); - return uri.ToString(); + return uri?.ToString(); } // Single AAS? if (record.GetSingleAas) { var uri = BuildUriForRepoSingleAAS(baseUri, record.AasId, encryptIds: record.EncryptIds); - return uri.ToString(); + return uri?.ToString(); + } + + // Single AAS by AssetLink? + if (record.GetAasByAssetLink) + { + var uri = BuildUriForRegistryAasByAssetLink(baseUri, record.AssetId, encryptIds: record.EncryptIds); + return uri?.ToString(); } // All Submodels? @@ -1631,28 +1754,28 @@ public static string BuildLocationFrom( { // if a skip has been requested, these AAS need to be loaded, as well var uri = BuildUriForRepoAllSubmodel(baseUri, record.PageLimit + record.PageSkip, cursor); - return uri.ToString(); + return uri?.ToString(); } // Single Submodel? if (record.GetSingleSubmodel) { var uri = BuildUriForRepoSingleSubmodel(baseUri, record.SmId, encryptIds: record.EncryptIds); - return uri.ToString(); + return uri?.ToString(); } // Single CD? if (record.GetSingleCD) { var uri = BuildUriForRepoSingleCD(baseUri, record.CdId, encryptIds: record.EncryptIds); - return uri.ToString(); + return uri?.ToString(); } // Query? if (record.ExecuteQuery) { var uri = BuildUriForRepoQuery(baseUri, record.QueryScript); - return uri.ToString(); + return uri?.ToString(); } } @@ -1668,21 +1791,21 @@ public static string BuildLocationFrom( { // if a skip has been requested, these AAS need to be loaded, as well var uri = BuildUriForRegistryAllAAS(baseUri, record.PageLimit + record.PageSkip, cursor); - return uri.ToString(); + return uri?.ToString(); } // Single AAS? if (record.GetSingleAas) { var uri = BuildUriForRegistrySingleAAS(baseUri, record.AasId, encryptIds: record.EncryptIds); - return uri.ToString(); + return uri?.ToString(); } - // Single AAS? + // Single AAS by AssetLink? if (record.GetAasByAssetLink) { var uri = BuildUriForRegistryAasByAssetLink(baseUri, record.AssetId, encryptIds: record.EncryptIds); - return uri.ToString(); + return uri?.ToString(); } } @@ -1746,9 +1869,6 @@ public static async Task PerformConnectExtendedDialogue( if (ticket?.ScriptMode == true) return true; - // reserve some states for the inner viewing routine - bool wrap = false; - // ok, go on .. var uc = new AnyUiDialogueDataModalPanel(caption); uc.ActivateRenderPanel(record, @@ -1825,7 +1945,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool) o) - record.SetQueryChoices(1); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllAas); else record.GetAllAas = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1842,7 +1962,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(2); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleAas); else record.GetSingleAas = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1873,7 +1993,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(3); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AasByAssetLink); else record.GetAasByAssetLink = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1904,7 +2024,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(4); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllSM); else record.GetAllSubmodel = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1921,7 +2041,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(5); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleSM); else record.GetSingleSubmodel = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1952,7 +2072,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(6); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleCD); else record.GetSingleCD = false; return new AnyUiLambdaActionModalPanelReRender(uc); @@ -1983,7 +2103,7 @@ public static async Task PerformConnectExtendedDialogue( colSpan: 2), (o) => { if ((bool)o) - record.SetQueryChoices(7); + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.Query); else record.ExecuteQuery = false; return new AnyUiLambdaActionModalPanelReRender(uc); From 8774e641621cf7767a67331c9364db8c6adb3c58 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 17 Nov 2024 00:55:31 +0100 Subject: [PATCH 30/99] * update --- .../AdminShellPackageEnvBase.cs | 305 ++++---- .../AdminShellPackageFileBasedEnv.cs | 208 ++--- src/AasxCsharpLibrary/AdminShellUtil.cs | 10 + .../ExtendSubmodelElementCollection.cs | 2 +- .../AnyUI/AnyUiMagickHelper.cs | 64 +- .../AasxWpfBaseUtils.cs | 26 +- src/AasxPackageExplorer/MainWindow.xaml | 248 +----- src/AasxPackageExplorer/MainWindow.xaml.cs | 154 ++-- src/AasxPackageExplorer/debug.MIHO.script | 2 +- .../options-debug.MIHO.json | 5 +- src/AasxPackageLogic/AnyUiforAas.cs | 1 + src/AasxPackageLogic/DispEditHelperBasics.cs | 2 +- src/AasxPackageLogic/DispEditHelperModules.cs | 150 ++-- src/AasxPackageLogic/ExplorerMenuFactory.cs | 32 +- src/AasxPackageLogic/IMainWindow.cs | 6 + src/AasxPackageLogic/MainWindowHeadless.cs | 26 +- .../PackageCentral/AasOnDemandEnvironment.cs | 41 +- .../AdminShellPackageDynamicFetchEnv.cs | 39 +- .../PackageCentral/PackageCentral.cs | 16 + .../PackageContainerHttpRepoSubset.cs | 734 ++++++++++-------- .../PackageContainerListBase.cs | 2 +- .../PackageContainerListHttpRestRepository.cs | 15 +- .../PackageContainerRepoItem.cs | 46 +- src/AasxPackageLogic/VisualAasxElements.cs | 1 + src/AasxPluginExportTable/Smt/ExportSmt.cs | 2 +- .../ImageMapAnyUiControl.cs | 6 +- .../SelectFromRepositoryFlyout.xaml.cs | 15 +- .../PackageContainerListOfListControl.xaml.cs | 61 +- src/BlazorExplorer/Data/AasxInfoBox.cs | 60 +- .../Data/BlazorSession.MainWindow.cs | 11 + 30 files changed, 1146 insertions(+), 1144 deletions(-) diff --git a/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs b/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs index 31f35b632..f40980862 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs @@ -11,6 +11,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.IO.Packaging; @@ -291,13 +292,22 @@ public virtual string Filename protected static WebProxy proxy = null; - public virtual Stream GetLocalStreamFromPackage( + /// + /// Checks for a file within the package or external, e.g. on a file space. + /// In derived classes, could use the AAS/ SM / IdShortPath attributes to + /// refer to files in registry/ repository. + /// + /// Local (within package) or external file. Should have a scheme. + /// Local file start with a leading slash. + /// For registry/ repository access. + /// For registry/ repository access. + /// For registry/ repository access. + /// Bytes or null + public virtual byte[] GetBytesFromPackageOrExternal( string uriString, string aasId = null, string smId = null, - string idShortPath = null, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) + string idShortPath = null) { // // this part of the functionality works on HTTP and absolute files and is @@ -379,19 +389,21 @@ public virtual Stream GetLocalStreamFromPackage( } response.EnsureSuccessStatusCode(); - var s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); - if (s.Length < 500) // indirect load? + var resBytes = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); + + // TODO (MIHO, 2024-11-09): looks like a hack from Andreas to + // detect indirection + if (resBytes.Length < 500) // indirect load? { - StreamReader reader = new StreamReader(s); - string json = reader.ReadToEnd(); + string json = System.Text.Encoding.UTF8.GetString(resBytes); var parsed = JObject.Parse(json); try { string url = parsed.SelectToken("url").Value(); response = hc.GetAsync(url).GetAwaiter().GetResult(); response.EnsureSuccessStatusCode(); - s = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + resBytes = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); } catch (Exception ex) { @@ -399,7 +411,7 @@ public virtual Stream GetLocalStreamFromPackage( } } - return s; + return resBytes; } // now, has to be file @@ -411,8 +423,8 @@ public virtual Stream GetLocalStreamFromPackage( { try { - var stream = System.IO.File.Open(sap.Path, mode, access); - return stream; + var resBytes = System.IO.File.ReadAllBytes(sap.Path); + return resBytes; } catch (Exception ex) { @@ -424,73 +436,133 @@ public virtual Stream GetLocalStreamFromPackage( return null; } - public virtual async Task GetLocalStreamFromPackageAsync( + /// + /// Checks for a file within the package or external, e.g. on a file space. + /// In derived classes, could use the AAS/ SM / IdShortPath attributes to + /// refer to files in registry/ repository. + /// + /// Local (within package) or external file. Should have a scheme. + /// Local file start with a leading slash. + /// For registry/ repository access. + /// For registry/ repository access. + /// For registry/ repository access. + /// Bytes or null + public virtual async Task GetBytesFromPackageOrExternalAsync( string uriString, string aasId = null, string smId = null, - string idShortPath = null, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) + string idShortPath = null) { await Task.Yield(); - return GetLocalStreamFromPackage(uriString, aasId, smId, idShortPath, mode, access); + return GetBytesFromPackageOrExternal(uriString, aasId, smId, idShortPath); } - public virtual void PrepareSupplementaryFileParameters(ref string targetDir, ref string targetFn) - { - } - - public virtual string AddSupplementaryFileToStore( - string sourcePath, string targetDir, string targetFn, bool embedAsThumb, - AdminShellPackageSupplementaryFile.SourceGetByteChunk sourceGetBytesDel = null, string useMimeType = null) + /// + /// Writes to a file within the package or to external registry/ repository. + /// Does not write to external files e.g. on a filespace! + /// In derived classes, could use the AAS/ SM / IdShortPath attributes to + /// refer to files in registry/ repository. + /// + /// Local (within package) or external file. Should have a scheme. + /// Local file start with a leading slash. + /// Data given by the bytes + /// For registry/ repository access. + /// For registry/ repository access. + /// For registry/ repository access. + /// True, if the file was successfully stored. + public virtual bool PutBytesToPackageOrExternal( + string uriString, + byte[] data, + string aasId = null, + string smId = null, + string idShortPath = null) { - return null; + // here, nothing to do, as external file storage is not allowed. + return false; } - public virtual byte[] GetByteArrayFromUriOrLocalPackage(string uriString) + /// + /// Writes to a file within the package or to external registry/ repository. + /// Does not write to external files e.g. on a filespace! + /// In derived classes, could use the AAS/ SM / IdShortPath attributes to + /// refer to files in registry/ repository. + /// + /// Local (within package) or external file. Should have a scheme. + /// Local file start with a leading slash. + /// Data given by the bytes + /// For registry/ repository access. + /// For registry/ repository access. + /// For registry/ repository access. + /// True, if the file was successfully stored. + public virtual async Task PutBytesToPackageOrExternalAsync( + string uriString, + byte[] data, + string aasId = null, + string smId = null, + string idShortPath = null) { - return null; + // here, nothing to do, as external file storage is not allowed. + await Task.Yield(); + return PutBytesToPackageOrExternal(uriString, data, aasId, smId, idShortPath); } - /// - /// Ensures: - ///
  • result == null || result.CanRead
- ///
- public Stream GetLocalThumbnailStream() + public virtual void PrepareSupplementaryFileParameters(ref string targetDir, ref string targetFn) { - Uri dummy = null; - var result = GetLocalThumbnailStream(ref dummy); - - // Post-condition - if (!(result == null || result.CanRead)) - { - throw new InvalidOperationException("Unexpected unreadable result stream"); - } - - return result; } - /// - /// Ensures: - ///
  • result == null || result.CanRead
- ///
- public virtual Stream GetLocalThumbnailStream(ref Uri thumbUri) + public virtual string AddSupplementaryFileToStore( + string sourcePath, string targetDir, string targetFn, bool embedAsThumb, + AdminShellPackageSupplementaryFile.SourceGetByteChunk sourceGetBytesDel = null, string useMimeType = null) { return null; } - public virtual Stream GetStreamFromUriOrLocalPackage(string uriString, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) + /// + /// Gets the thumbnail data of the local package (and only the local package!) + /// + public virtual byte[] GetLocalThumbnailBytes(ref Uri thumbUri) { return null; } /// - /// This is intended to be the "new" one + /// Gets the thumbnail data of the local package or (1st prio) the AAS with the + /// given id. + /// Note: does not access remote content from registry/ repository! /// - public virtual Stream GetThumbnailStreamFromAasOrPackage(string aasId) + public virtual byte[] GetThumbnailBytesFromAasOrPackage(string aasId) { + // find aas? + var aas = AasEnv?.FindAasById(aasId); + if (aas?.AssetInformation?.DefaultThumbnail?.Path?.HasContent() == true) + { + try + { + // Note: could also use http://... + var bytes = GetBytesFromPackageOrExternal(uriString: aas.AssetInformation.DefaultThumbnail.Path); + if (bytes != null) + return bytes; + } + catch (Exception ex) + { + LogInternally.That.CompletelyIgnoredError(ex); + } + } + + // or local package? + try + { + Uri dummy = null; + var bytes = GetLocalThumbnailBytes(ref dummy); + if (bytes != null) + return bytes; + } + catch (Exception ex) + { + LogInternally.That.CompletelyIgnoredError(ex); + } + + // no return null; } @@ -516,33 +588,27 @@ public virtual string MakePackageFileAvailableAsTempFile(string packageUri, bool return null; // get input stream - using (var input = GetLocalStreamFromPackage(packageUri)) - { - // any - if (input == null) - return null; - - // generate tempfile name - string tempext = System.IO.Path.GetExtension(packageUri); - string temppath = System.IO.Path.GetTempFileName().Replace(".tmp", tempext); + var inputBytes = GetBytesFromPackageOrExternal(packageUri); + if (inputBytes == null) + return null; - // maybe modify tempfile name? - if (keepFilename) - { - var masterFn = System.IO.Path.GetFileNameWithoutExtension(packageUri); - var tmpDir = System.IO.Path.GetDirectoryName(temppath); - var tmpFnExt = System.IO.Path.GetFileName(temppath); + // generate tempfile name + string tempext = System.IO.Path.GetExtension(packageUri); + string temppath = System.IO.Path.GetTempFileName().Replace(".tmp", tempext); - temppath = System.IO.Path.Combine(tmpDir, "" + masterFn + "_" + tmpFnExt); - } + // maybe modify tempfile name? + if (keepFilename) + { + var masterFn = System.IO.Path.GetFileNameWithoutExtension(packageUri); + var tmpDir = System.IO.Path.GetDirectoryName(temppath); + var tmpFnExt = System.IO.Path.GetFileName(temppath); - // copy to temp file - using (var temp = System.IO.File.OpenWrite(temppath)) - { - input.CopyTo(temp); - return temppath; - } + temppath = System.IO.Path.Combine(tmpDir, "" + masterFn + "_" + tmpFnExt); } + + // copy to temp file + System.IO.File.WriteAllBytes(temppath, inputBytes); + return temppath; } /// @@ -563,33 +629,27 @@ public virtual async Task MakePackageFileAvailableAsTempFileAsync( return null; // get input stream - using (var input = await GetLocalStreamFromPackageAsync(packageUri, aasId, smId, idShortPath)) - { - // ok? - if (input == null) - return null; - - // generate tempfile name - string tempext = System.IO.Path.GetExtension(packageUri); - string temppath = System.IO.Path.GetTempFileName().Replace(".tmp", tempext); + var inputBytes = await GetBytesFromPackageOrExternalAsync(packageUri, aasId, smId, idShortPath); + if (inputBytes == null) + return null; - // maybe modify tempfile name? - if (keepFilename) - { - var masterFn = System.IO.Path.GetFileNameWithoutExtension(packageUri); - var tmpDir = System.IO.Path.GetDirectoryName(temppath); - var tmpFnExt = System.IO.Path.GetFileName(temppath); + // generate tempfile name + string tempext = System.IO.Path.GetExtension(packageUri); + string temppath = System.IO.Path.GetTempFileName().Replace(".tmp", tempext); - temppath = System.IO.Path.Combine(tmpDir, "" + masterFn + "_" + tmpFnExt); - } + // maybe modify tempfile name? + if (keepFilename) + { + var masterFn = System.IO.Path.GetFileNameWithoutExtension(packageUri); + var tmpDir = System.IO.Path.GetDirectoryName(temppath); + var tmpFnExt = System.IO.Path.GetFileName(temppath); - // copy to temp file - using (var temp = System.IO.File.OpenWrite(temppath)) - { - input.CopyTo(temp); - return temppath; - } + temppath = System.IO.Path.Combine(tmpDir, "" + masterFn + "_" + tmpFnExt); } + + // copy to temp file + await System.IO.File.WriteAllBytesAsync(temppath, inputBytes); + return temppath; } public virtual bool SaveAs( @@ -642,55 +702,6 @@ public virtual void Dispose() // Binary file read + write // - public async Task GetByteArrayFromExternalInternalUri(string uri) - { - // split uri and access - var sap = AdminShellUtil.GetSchemeAndPath(uri); - if (sap == null) - return null; - - // directly a HTTP resource? - if (sap.Scheme.StartsWith("http")) - { - try - { - using (var client = new HttpClient()) - { - var ba = await client.GetByteArrayAsync(uri); - return ba; - } - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - return null; - } - } - - // no other schemes now - if (sap.Scheme != "file") - return null; - - // check if package local file - if (IsLocalFile(sap.Path)) - { - return GetByteArrayFromUriOrLocalPackage(sap.Path); - } - - // OK, assume a file accessible to this computer - try - { - var ba = await System.IO.File.ReadAllBytesAsync(sap.Path); - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - } - - // nope - return null; - } - public async Task PutByteArrayToExternalUri(string uri, byte[] ba) { // split uri and access diff --git a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs index cd56c5bbb..21ad10dd4 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs @@ -1055,64 +1055,37 @@ public void BackupInDir(string backupDir, int maxFiles) } } - public override Stream GetStreamFromUriOrLocalPackage(string uriString, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) - { - // local - if (IsLocalFile(uriString)) - return GetLocalStreamFromPackage(uriString, mode: mode, access: access); - - // no .. - return System.IO.File.Open(uriString, mode, access); - } - - public override byte[] GetByteArrayFromUriOrLocalPackage(string uriString) - { - try - { - using (var input = GetStreamFromUriOrLocalPackage(uriString)) - { - using (MemoryStream ms = new MemoryStream()) - { - input.CopyTo(ms); - return ms.ToArray(); - } - } - } - catch (Exception ex) - { - AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); - return null; - } - } - public override bool IsLocalFile(string uriString) { - // access + // look at the uri + var sap = AdminShellUtil.GetSchemeAndPath(uriString); + if (sap == null) + return false; + if (sap.Scheme != "file") + return false; + + // look at package / file path if (_openPackage == null) return false; - if (uriString == null || uriString == "" || !uriString.StartsWith("/")) + if (sap.Path == null || !sap.Path.StartsWith("/")) return false; - // check + // check further var isLocal = _openPackage.PartExists(new Uri(uriString, UriKind.RelativeOrAbsolute)); return isLocal; } - public override Stream GetLocalStreamFromPackage( + public override byte[] GetBytesFromPackageOrExternal( string uriString, string aasId = null, string smId = null, - string idShortPath = null, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) + string idShortPath = null) { // IMPORTANT! First try to use the base implementation to get an stream to // HTTP or ABSOLUTE file - var absStream = base.GetLocalStreamFromPackage(uriString, mode: mode, access: access); - if (absStream != null) - return absStream; + var absBytes = base.GetBytesFromPackageOrExternal(uriString); + if (absBytes != null) + return absBytes; // now, split uri string (again) for ourselves var sap = AdminShellUtil.GetSchemeAndPath(uriString); @@ -1133,7 +1106,99 @@ public override Stream GetLocalStreamFromPackage( if (part == null) throw (new Exception( string.Format($"Cannot access part {uriString} in {_fn}. Aborting!"))); - return part.GetStream(mode, access); + + // read bytes + using (var stream = part.GetStream(FileMode.Open, FileAccess.Read)) + using (MemoryStream ms = new MemoryStream()) + { + stream.CopyTo(ms); + return ms.ToArray(); + } + } + + public override async Task GetBytesFromPackageOrExternalAsync( + string uriString, + string aasId = null, + string smId = null, + string idShortPath = null) + { + // IMPORTANT! First try to use the base implementation to get an stream to + // HTTP or ABSOLUTE file + var absBytes = await base.GetBytesFromPackageOrExternalAsync(uriString); + if (absBytes != null) + return absBytes; + + // now, split uri string (again) for ourselves + var sap = AdminShellUtil.GetSchemeAndPath(uriString); + if (sap == null) + return null; + + // now, it has to be an package file + if (_openPackage == null) + throw (new Exception(string.Format($"AASX Package {_fn} not opened. Aborting!"))); + + // exist + var puri = new Uri(sap.Path, UriKind.RelativeOrAbsolute); + if (!_openPackage.PartExists(puri)) + throw (new Exception(string.Format($"AASX Package has no part {sap.Path}. Aborting!"))); + + // get part + var part = _openPackage.GetPart(puri); + if (part == null) + throw (new Exception( + string.Format($"Cannot access part {sap.Path} in {_fn}. Aborting!"))); + + // read bytes + using (var stream = part.GetStream(FileMode.Open, FileAccess.Read)) + using (MemoryStream ms = new MemoryStream()) + { + await stream.CopyToAsync(ms); + return ms.ToArray(); + } + } + + public override bool PutBytesToPackageOrExternal( + string uriString, + byte[] data, + string aasId = null, + string smId = null, + string idShortPath = null) + { + // split uri string + var sap = AdminShellUtil.GetSchemeAndPath(uriString); + if (sap == null) + return false; + + // if not a file, refer to base + // also, if package is not open or is no local file + if (sap.Scheme != "file" + || _openPackage == null + || !IsLocalFile(uriString)) + { + return base.PutBytesToPackageOrExternal(uriString, data, aasId, smId, idShortPath); + } + + // now, we're supposed to handle it! + + // exist + var puri = new Uri(sap.Path, UriKind.RelativeOrAbsolute); + if (!_openPackage.PartExists(puri)) + throw (new Exception(string.Format($"AASX Package has no part {sap.Path}. Aborting!"))); + + // get part + var part = _openPackage.GetPart(puri); + if (part == null) + throw (new Exception( + string.Format($"Cannot access part {sap.Path} in {_fn}. Aborting!"))); + + // read bytes + using (var stream = part.GetStream(FileMode.Create, FileAccess.Write)) + using (MemoryStream ms = new MemoryStream(data)) + { + ms.CopyTo(stream); + } + + return true; } public async Task ReplaceSupplementaryFileInPackageAsync(string sourceUri, string targetFile, string targetContentType, Stream fileContent) @@ -1152,7 +1217,7 @@ public async Task ReplaceSupplementaryFileInPackageAsync(string sourceUri, strin fileContent.Position = 0; using (Stream dest = packagePart.GetStream()) { - fileContent.CopyTo(dest); + await fileContent.CopyToAsync(dest); } } @@ -1186,11 +1251,7 @@ public long GetStreamSizeFromPackage(string uriString) return res; } - /// - /// Ensures: - ///
  • result == null || result.CanRead
- ///
- public override Stream GetLocalThumbnailStream(ref Uri thumbUri) + public override byte[] GetLocalThumbnailBytes(ref Uri thumbUri) { // access if (_openPackage == null) @@ -1212,51 +1273,14 @@ public override Stream GetLocalThumbnailStream(ref Uri thumbUri) if (thumbPart == null) throw (new Exception("Unable to find AASX thumbnail. Aborting!")); - var result = thumbPart.GetStream(FileMode.Open); - - // Post-condition - if (!(result == null || result.CanRead)) - { - throw new InvalidOperationException("Unexpected unreadable result stream"); - } - - return result; - } - - public override Stream GetThumbnailStreamFromAasOrPackage(string aasId) - { - // find aas? - var aas = AasEnv?.FindAasById(aasId); - if (aas?.AssetInformation?.DefaultThumbnail?.Path?.HasContent() == true) - { - try - { - // Note: could also use http://... - var s1 = GetLocalStreamFromPackage(uriString: aas.AssetInformation.DefaultThumbnail.Path); - if (s1 != null) - return s1; - } catch (Exception ex) - { - LogInternally.That.CompletelyIgnoredError(ex); - } - } - - // or local package? - try - { - Uri dummy = null; - var s2 = GetLocalThumbnailStream(ref dummy); - if (s2 != null) - return s2; - } - catch (Exception ex) + // read bytes + using (var stream = thumbPart.GetStream(FileMode.Open, FileAccess.Read)) + using (MemoryStream ms = new MemoryStream()) { - LogInternally.That.CompletelyIgnoredError(ex); + stream.CopyTo(ms); + return ms.ToArray(); } - - // no - return null; - } + } public override ListOfAasSupplementaryFile GetListOfSupplementaryFiles() { diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index cbca0ab3c..dcae2210b 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -1509,6 +1509,16 @@ public static string GetDefaultLngIso639() return DefaultLngIso639; } + // + // Bytes + // + + public static string GetStringFromBytes(byte[] byteArray) + { + if (byteArray == null) + return null; + return System.Text.Encoding.UTF8.GetString(byteArray); + } } } diff --git a/src/AasxCsharpLibrary/Extensions/ExtendSubmodelElementCollection.cs b/src/AasxCsharpLibrary/Extensions/ExtendSubmodelElementCollection.cs index 47607c9d0..27fc79e7a 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendSubmodelElementCollection.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendSubmodelElementCollection.cs @@ -77,7 +77,7 @@ public static object AddChild(this SubmodelElementCollection submodelElementColl public static T FindFirstIdShortAs(this SubmodelElementCollection submodelElementCollection, string idShort) where T : ISubmodelElement { - var submodelElement = submodelElementCollection.Value.Where(sme => (sme != null) && (sme is T) && sme.IdShort.Equals(idShort, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + var submodelElement = submodelElementCollection.Value.Where(sme => (sme != null) && (sme is T) && sme.IdShort?.Equals(idShort, StringComparison.OrdinalIgnoreCase) == true).FirstOrDefault(); return (T)submodelElement; } diff --git a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs index f65846dd7..be6108786 100644 --- a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs +++ b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs @@ -121,14 +121,13 @@ public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageEnvBase try { - var thumbStream = package.GetLocalStreamFromPackage(path); - if (thumbStream == null) + var inputBytes = package.GetBytesFromPackageOrExternal(path); + if (inputBytes == null) return null; // load image - var bi = new MagickImage(thumbStream); + var bi = new MagickImage(inputBytes); var binfo = CreateAnyUiBitmapInfo(bi); - thumbStream.Close(); // give this back return binfo; @@ -141,27 +140,28 @@ public static AnyUiBitmapInfo LoadBitmapInfoFromPackage(AdminShellPackageEnvBase return null; } - public static AnyUiBitmapInfo LoadBitmapInfoFromStream(Stream stream) - { - if (stream == null) - return null; - - try - { - // load image - var bi = new MagickImage(stream); - var binfo = CreateAnyUiBitmapInfo(bi); - - // give this back - return binfo; - } - catch (Exception ex) - { - AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); - } - - return null; - } + // DEPRECATED + //public static AnyUiBitmapInfo LoadBitmapInfoFromStream(Stream stream) + //{ + // if (stream == null) + // return null; + + // try + // { + // // load image + // var bi = new MagickImage(stream); + // var binfo = CreateAnyUiBitmapInfo(bi); + + // // give this back + // return binfo; + // } + // catch (Exception ex) + // { + // AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); + // } + + // return null; + //} // TODO (MIHO, 2023-02-23): make the whole thing async!! @@ -177,9 +177,9 @@ public static async Task MakePreviewFromPackageOrUrlAsync( try { - System.IO.Stream thumbStream = null; + byte[] thumbBytes = null; if (true /*= package?.IsLocalFile(path)*/) - thumbStream = await package.GetLocalStreamFromPackageAsync(path, aasId, smId, idShortPath); + thumbBytes = await package.GetBytesFromPackageOrExternalAsync(path, aasId, smId, idShortPath); else { // try download @@ -209,7 +209,7 @@ public static async Task MakePreviewFromPackageOrUrlAsync( #endif } - if (thumbStream == null) + if (thumbBytes == null) return null; using (var images = new MagickImageCollection()) @@ -220,15 +220,13 @@ public static async Task MakePreviewFromPackageOrUrlAsync( settings.FrameCount = 1; // Number of pages // Read only the first page of the pdf file - images.Read(thumbStream, settings); + images.Read(thumbBytes, settings); if (images.Count > 0 && images[0] is MagickImage img) { res = CreateAnyUiBitmapInfo(img); } } - - thumbStream.Close(); } catch (Exception ex) { @@ -319,13 +317,13 @@ public async Task TickToLoad() // now try loading if (job is DelayedFileContentForFileElement jobfc) { - var stream = await jobfc.Package?.GetLocalStreamFromPackageAsync( + var inputBytes = await jobfc.Package?.GetBytesFromPackageOrExternalAsync( uriString: jobfc.FileUri, aasId: "" + jobfc.AasId, smId: "" + jobfc.SmId, idShortPath: jobfc.IdShortPath); - var bi = AnyUiGdiHelper.LoadBitmapInfoFromStream(stream); + var bi = AnyUiGdiHelper.LoadBitmapInfoFromBytes(inputBytes); if (bi != null) { diff --git a/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs b/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs index 5695e2c1f..8d2a8e451 100644 --- a/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs +++ b/src/AasxIntegrationBaseWpf/AasxWpfBaseUtils.cs @@ -144,22 +144,22 @@ public static BitmapImage LoadBitmapImageFromPackage(AdminShellPackageEnvBase pa try { - var thumbStream = package.GetLocalStreamFromPackage(path); - if (thumbStream == null) + var imgBytes = package.GetBytesFromPackageOrExternal(path); + if (imgBytes == null) return null; // load image - var bi = new BitmapImage(); - bi.BeginInit(); - bi.CacheOption = BitmapCacheOption.OnLoad; - bi.StreamSource = thumbStream; - bi.EndInit(); - - thumbStream.Close(); - - // note: no closing of bi required, as BitmapImage (OnLoad!) will close it! - // give this back - return bi; + using (var ms = new System.IO.MemoryStream(imgBytes)) + { + var bi = new BitmapImage(); + bi.BeginInit(); + bi.CacheOption = BitmapCacheOption.OnLoad; + bi.StreamSource = ms; + bi.EndInit(); + // note: no closing of bi required, as BitmapImage (OnLoad!) will close it! + // give this back + return bi; + } } catch (Exception ex) { diff --git a/src/AasxPackageExplorer/MainWindow.xaml b/src/AasxPackageExplorer/MainWindow.xaml index 701a9e06c..aec553ef9 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml +++ b/src/AasxPackageExplorer/MainWindow.xaml @@ -1,4 +1,4 @@ - - @@ -35,123 +34,6 @@ - - - - - - @@ -190,136 +72,8 @@ - - - - - diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index b451d26e8..0747d7630 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -743,23 +743,23 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie if (PackageCentral.MainAvailable) try { - var thumbStream = PackageCentral.Main.GetThumbnailStreamFromAasOrPackage(tvlaas.theAas.Id); - if (thumbStream != null) - { - // load image - var bi = new BitmapImage(); - bi.BeginInit(); + var bytes = PackageCentral.Main.GetThumbnailBytesFromAasOrPackage(tvlaas.theAas.Id); + if (bytes != null) + using (var ms = new MemoryStream(bytes)) + { + // load image + var bi = new BitmapImage(); + bi.BeginInit(); - // See https://stackoverflow.com/a/5346766/1600678 - bi.CacheOption = BitmapCacheOption.OnLoad; + // See https://stackoverflow.com/a/5346766/1600678 + bi.CacheOption = BitmapCacheOption.OnLoad; - bi.StreamSource = thumbStream; - bi.EndInit(); + bi.StreamSource = ms; + bi.EndInit(); - this.AssetPic.Source = bi; - picFound = true; - thumbStream.Close(); - } + this.AssetPic.Source = bi; + picFound = true; + } } catch (Exception ex) { @@ -1729,11 +1729,11 @@ private void UiHandleReRenderAnyUiInEntityPanel( } } - private async Task UiSearchRepoAndExtendEnvironment( + public async Task UiSearchRepoAndExtendEnvironmentAsync( AdminShellPackageEnvBase packEnv, Aas.IReference workRef) { - Task.Yield(); + await Task.Yield(); // access if (packEnv == null || workRef?.IsValid() != true) @@ -1789,11 +1789,13 @@ private void UiHandleReRenderAnyUiInEntityPanel( record); var newIdfs = new List(); + var loadedIdfs = new List(); var loadRes = await PackageContainerHttpRepoSubset.LoadFromSourceInternalAsync( fullItemLocation: fullItemLocation, targetEnv: packEnv, loadNew: false, trackNewIdentifiables: newIdfs, + trackLoadedIdentifiables: loadedIdfs, containerOptions: containerOptions, runtimeOptions: PackageCentral.CentralRuntimeOptions); @@ -1842,7 +1844,15 @@ private async Task UiHandleNavigateTo( if (PackageCentral.MainAvailable && PackageCentral.Main.AasEnv != null) bo = PackageCentral.Main.AasEnv.FindReferableByReference(work); - // if not, may be in aux package + // Prio 2: check for connected repositories + // (An actual "up-to-date" hit in repo in more valuable than a stored container on file space) + if (firstTime && bo == null) + { + bo = await UiSearchRepoAndExtendEnvironmentAsync(PackageCentral.Main, work); + firstTime = false; + } + + // if not, may be in aux package (not sure, if this works) if (bo == null && PackageCentral.Aux != null && PackageCentral.Aux.AasEnv != null) bo = PackageCentral.Aux.AasEnv.FindReferableByReference(work); @@ -1861,15 +1871,7 @@ private async Task UiHandleNavigateTo( bo = boInfo?.BusinessObject; } - // TODO .. try search in connected repositories!!! - // Note: only now, after checking the "cheaper" alternatives - if (firstTime && bo == null) - { - bo = await UiSearchRepoAndExtendEnvironment(PackageCentral.Main, work); - firstTime = false; - } - - // still yes? + // anything found? if (bo != null) { // try to look up in visual elements @@ -2827,18 +2829,32 @@ private async Task ButtonHistory_ObjectRequested(object sender, VisualElementHis } } + // no? Try to find the business object + var bo = hi?.VisualElement?.GetMainDataObject(); + if (bo != null) + { + if (DisplayElements.TrySelectMainDataObject(bo, wishExpanded: true, alsoDereferenceObjects: true)) + { + // fake selection + RedrawElementView(); + DisplayElements.Refresh(); + TakeOverContentEnable(false); + + // done + return; + } + } + // no? .. is there a way to another file? if (PackageCentral.Repositories != null && hi?.ReferableAasId != null && hi.ReferableReference != null) { - ; - // try lookup file in file repository var fi = PackageCentral.Repositories.FindByAasId(hi.ReferableAasId.Trim()); if (fi == null) { - Log.Singleton.Error( - $"Cannot lookup aas id {hi.ReferableAasId} in file repository."); + Log.Singleton.Info( + $"History: Cannot lookup aas id {hi.ReferableAasId} in file repository."); return; } @@ -2846,7 +2862,7 @@ private async Task ButtonHistory_ObjectRequested(object sender, VisualElementHis var sri = ListOfVisualElement.StripSupplementaryReferenceInformation(hi.ReferableReference); // load it (safe) - object bo = null; + bo = null; try { var boInfo = await LoadFromFileRepository(fi, sri.CleanReference); @@ -3179,6 +3195,56 @@ private void ShowContent_Click(object sender, RoutedEventArgs e) { if (sender == ShowContent && _showContentElement != null && PackageCentral.MainAvailable) { + // + // Text edit of BLOB? + // + + if (this._showContentElement is Aas.IBlob blb + && MainMenu?.IsChecked("EditMenu") == true + && AdminShellUtil.CheckForTextContentType(blb.ContentType)) + { + Log.Singleton.Info("Trying edit multiline content from {0} ..", blb.IdShort); + try + { + var uc = new AnyUiDialogueDataTextEditor( + caption: $"Edit Blob '{"" + blb.IdShort}'", + mimeType: blb.ContentType, + text: Encoding.Default.GetString(blb.Value ?? new byte[0])); + if (this.DisplayContext.StartFlyoverModal(uc)) + { + blb.Value = Encoding.Default.GetBytes(uc.Text); + RedrawElementView(); + } + } + catch (Exception ex) + { + Log.Singleton.Error( + ex, $"When editing content from {blb.IdShort}, an error occurred"); + return; + } + Log.Singleton.Info("Content from {0} edited.", blb.IdShort); + return; + } + + if (this._showContentElement is Aas.IFile file + && MainMenu?.IsChecked("EditMenu") == true + && AdminShellUtil.CheckForTextContentType(file.ContentType)) + { + Log.Singleton.Info("Trying edit multiline content from {0} ..", file.IdShort); + + DispEditHelperModules.DisplayOrEditEntityFileResource_EditTextFile( + DisplayContext, PackageCentral.Main, + file.ContentType, + file.Value); + + Log.Singleton.Info("Content from {0} edited.", file.IdShort); + return; + } + + // + // Display? + // + Tuple contentFound = null; if (_showContentElement is Aas.IFile scFile) contentFound = new Tuple(scFile.Value, scFile.ContentType); @@ -3232,32 +3298,6 @@ private void ShowContent_Click(object sender, RoutedEventArgs e) } Log.Singleton.Info("Content {0} displayed.", contentFound.Item1); } - - if (this._showContentElement is Aas.IBlob blb - && MainMenu?.IsChecked("EditMenu") == true - && AdminShellUtil.CheckForTextContentType(blb.ContentType)) - { - Log.Singleton.Info("Trying edit multiline content from {0} ..", blb.IdShort); - try - { - var uc = new AnyUiDialogueDataTextEditor( - caption: $"Edit Blob '{"" + blb.IdShort}'", - mimeType: blb.ContentType, - text: Encoding.Default.GetString(blb.Value ?? new byte[0])); - if (this.DisplayContext.StartFlyoverModal(uc)) - { - blb.Value = Encoding.Default.GetBytes(uc.Text); - RedrawElementView(); - } - } - catch (Exception ex) - { - Log.Singleton.Error( - ex, $"When editing content from {blb.IdShort}, an error occurred"); - return; - } - Log.Singleton.Info("Content from {0} edited.", blb.IdShort); - } } } diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 9bf83079e..1d69c5628 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -10,7 +10,7 @@ // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); // Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); -Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); +// Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); // Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 7f1bc815a..df34d9c6a 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -52,7 +52,7 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\MTP\\test-data-SMT_MTP.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\00_FestoDemoBox-Module-2-Kopie.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work_spiel.aasx", - "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\a33.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v02.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", @@ -69,7 +69,8 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\robotic_cell_for_demo_suitcase_new-v3.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02006-2-1_Template_Digital Nameplate_V3_Demo_ExportSMT - Kopie.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_play_02.aasx", - // "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", + "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", + // "AasxRepositoryFn": "C:\\Users\\homi0002\\Desktop\\test3\\new-aasx-repo.json" , // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\smt_pcn\\SMT_ProductChangeNotification_Draft_play_02.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\dnp30\\IDTA 02006-3-0_Template_Digital Nameplate_AsciiDoc_Draft_v06.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\contactv11\\IDTA 02002-1-1_Template_ContactInformation_AsciiDoc_Draft_v04.aasx", diff --git a/src/AasxPackageLogic/AnyUiforAas.cs b/src/AasxPackageLogic/AnyUiforAas.cs index daa6bcfa7..38e8923ea 100644 --- a/src/AasxPackageLogic/AnyUiforAas.cs +++ b/src/AasxPackageLogic/AnyUiforAas.cs @@ -278,6 +278,7 @@ public class AnyUiDialogueDataSelectFromRepository : AnyUiDialogueDataBase public IList Items = null; public PackageContainerRepoItem ResultItem = null; + public string ResultId = null; public AnyUiDialogueDataSelectFromRepository( string caption = "", diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index 16b626ee1..088d47da0 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -1155,7 +1155,7 @@ public async Task SmartRefactorSme_PostProcess( if (newSme is Aas.IBlob newBlob && oldSme is Aas.IFile oldFile) { // get file contents from a file - var ba = packEnv?.GetByteArrayFromUriOrLocalPackage(oldFile.Value); + var ba = await packEnv?.GetBytesFromPackageOrExternalAsync(oldFile.Value); if (ba == null || ba.Length < 1) return false; diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 0463f0b06..3861be7df 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -37,6 +37,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Threading.Tasks; using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; using System.Security.Cryptography; +using System.Collections; namespace AasxPackageLogic { @@ -2396,7 +2397,6 @@ public void DisplayOrEditEntitySubmodelRef(AnyUiStackPanel stack, // File / Resource attributes // -#if __not_required_obsolete_by_PUT_attachment_operation public class CentralizeFilesRecord { public string CentralStoreUri = ""; @@ -2561,7 +2561,7 @@ public async Task PerformCentralizeFileExecution( return false; // try accessing it - var ba = await packEnv.GetByteArrayFromExternalInternalUri(filePath); + var ba = await packEnv.GetBytesFromPackageOrExternalAsync(filePath); if (ba == null || ba.Length < 1) { Log.Singleton.Error("Centralize file: cannot read file: {0}", filePath); @@ -2623,7 +2623,74 @@ public async Task PerformCentralizeFileExecution( return true; } -#endif + + public static bool DisplayOrEditEntityFileResource_EditTextFile( + AnyUiContextBase context, + AdminShellPackageEnvBase env, + string valueContent, + string valuePath) + { + // access + if (env == null || context == null) + return false; + + // try + try + { + // try find .. + var psfs = env.GetListOfSupplementaryFiles(); + var psf = psfs?.FindByUri(valuePath); + if (psf == null) + { + Log.Singleton.Error( + $"Not able to locate supplementary file {valuePath} for edit. " + + $"Aborting!"); + return false; + } + + // try read .. + Log.Singleton.Info($"Reading text-file {valuePath} .."); + var contents = AdminShellUtil.GetStringFromBytes( + env.GetBytesFromPackageOrExternal(valuePath)); + + // test + if (contents == null) + { + Log.Singleton.Error( + $"Not able to read contents from supplmentary file {valuePath} " + + $"for edit. Aborting!"); + return false; + } + + // edit + var uc = new AnyUiDialogueDataTextEditor( + caption: $"Edit text-file '{valuePath}'", + mimeType: valueContent, + text: contents); + if (!context.StartFlyoverModal(uc)) + return false; + + // save + byte[] bytes = Encoding.ASCII.GetBytes(uc.Text); + try + { + // TODO: add IdShortPath !! + env.PutBytesToPackageOrExternal( + valuePath, bytes); + } + catch (Exception ex) + { + Log.Singleton.Error(ex, "when storing contents to text-file: " + valuePath); + } + } + catch (Exception ex) + { + Log.Singleton.Error( + ex, $"Edit text-file {valuePath} in package."); + } + + return true; + } public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, AdminShellPackageEnvBase packEnv, @@ -2734,10 +2801,8 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, "Creates a text file and adds it to the AAS environment.") .AddAction("edit-text", "Edit text file", "Edits the associated text file and updates it to the AAS environment.") -#if __not_required_obsolete_by_PUT_attachment_operation .AddAction("centralize-file", "Centralize file", "Rename file, copy it to central file storage and potentially delete supplemental file.") -#endif , ticketActionAsync: async (buttonNdx, ticket) => @@ -2756,7 +2821,7 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, if (psf == null) { Log.Singleton.Error( - $"Not able to locate supplmentary file {valuePath} for removal! " + + $"Not able to locate supplementary file {valuePath} for removal! " + $"Aborting!"); } else @@ -2858,73 +2923,15 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, if (buttonNdx == 2) { - try - { - // try find .. - var psfs = packages.Main.GetListOfSupplementaryFiles(); - var psf = psfs?.FindByUri(valuePath); - if (psf == null) - { - Log.Singleton.Error( - $"Not able to locate supplmentary file {valuePath} for edit. " + - $"Aborting!"); - return new AnyUiLambdaActionNone(); - } - - // try read .. - Log.Singleton.Info($"Reading text-file {valuePath} .."); - string contents; - using (var stream = packages.Main.GetStreamFromUriOrLocalPackage(valuePath)) - { - using (var sr = new StreamReader(stream)) - { - // read contents - contents = sr.ReadToEnd(); - } - } - - // test - if (contents == null) - { - Log.Singleton.Error( - $"Not able to read contents from supplmentary file {valuePath} " + - $"for edit. Aborting!"); - return new AnyUiLambdaActionNone(); - } - - // edit - var uc = new AnyUiDialogueDataTextEditor( - caption: $"Edit text-file '{valuePath}'", - mimeType: valueContent, - text: contents); - if (!this.context.StartFlyoverModal(uc)) - return new AnyUiLambdaActionNone(); - - // save - using (var stream = packages.Main.GetStreamFromUriOrLocalPackage( - valuePath, FileMode.OpenOrCreate, FileAccess.ReadWrite)) - { - using (var sw = new StreamWriter(stream)) - { - // write contents - sw.Write(uc.Text); - } - } - - // value event - this.AddDiaryEntry(containingObject, new DiaryEntryUpdateValue()); - } - catch (Exception ex) - { - Log.Singleton.Error( - ex, $"Edit text-file {valuePath} in package."); - } - - // reshow - return new AnyUiLambdaActionRedrawEntity(); + if (DisplayOrEditEntityFileResource_EditTextFile( + context, packages.Main, + valueContent: valueContent, + valuePath: valuePath)) + return new AnyUiLambdaActionRedrawEntity(); + else + return new AnyUiLambdaActionNone(); } -#if __not_required_obsolete_by_PUT_attachment_operation if (buttonNdx == 3 && valuePath.HasContent()) { var changed = false; @@ -2951,7 +2958,6 @@ await PerformCentralizeFileExecution( if (changed) return new AnyUiLambdaActionRedrawAllElements(nextFocus: relatedReferable); } -#endif return new AnyUiLambdaActionNone(); }); diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index d5c4f3ba3..22ceed980 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -53,7 +53,12 @@ public static AasxMenu CreateMainMenu() help: "Open existing AASX package.", args: new AasxMenuListOfArgDefs() .Add("File", "Source filename including a path and extension.")) - .AddWpfBlazor(name: "ConnectIntegrated", header: "Connect …", inputGesture: "Ctrl+Shift+I") + .AddWpfBlazor(name: "FileRepoQuery", header: "Query open repositories/ registries …", inputGesture: "F12", + help: "Selects and repository item (AASX) from the open AASX file repositories.", + args: new AasxMenuListOfArgDefs() + .Add("Index", "Zero-based integer index to the list of all open repos.") + .Add("AAS", "String with AAS-Id") + .Add("Asset", "String with Asset-Id.")) .AddWpfBlazor(name: "Save", header: "_Save", inputGesture: "Ctrl+S") .AddWpfBlazor(name: "SaveAs", header: "_Save as …", help: "Saves current package to given file name and typr", @@ -103,9 +108,14 @@ public static AasxMenu CreateMainMenu() .AddWpfBlazor(name: "CloseAux", header: "Close Auxiliary AAS") .AddSeparator() .AddMenu(header: "Further connect options …", childs: (new AasxMenu()) - .AddWpfBlazor(name: "ConnectSecure", header: "Secure Connect …", inputGesture: "Ctrl+Shift+S") - .AddWpfBlazor(name: "ConnectOpcUa", header: "Connect via OPC-UA …") - .AddWpfBlazor(name: "ConnectRest", header: "Connect via REST …", inputGesture: "F6")) + .AddWpfBlazor(name: "ConnectSecure", header: "Secure Connect (deprecated) …") + .AddWpfBlazor(name: "ConnectOpcUa", header: "Connect via OPC-UA (deprecated) …") + .AddWpfBlazor(name: "ConnectRest", header: "Connect via REST (deprecated) …") + .AddWpfBlazor(name: "ConnectIntegrated", header: "Connect integrated (deprecated) …") + .AddWpfBlazor(name: "FileRepoConnectRepository", header: "Connect HTTP/REST repository … (deprecated, for Event demo)", + help: "Connects to an online repository via HTTP/REST.", + args: new AasxMenuListOfArgDefs() + .Add("Endpoint", "Endpoint of repo (without \"/server/listaas\")."))) .AddSeparator() .AddMenu(header: "API for Registry and Repository …", childs: (new AasxMenu()) .AddWpf(name: "AddBaseAddress", header: "Add preset for base address …") @@ -122,20 +132,8 @@ public static AasxMenu CreateMainMenu() help: "Opens an existing AASX file repository and adds it to the list of open repos.", args: new AasxMenuListOfArgDefs() .Add("File", "Path and filename of existing AASX file repository.")) - .AddWpfBlazor(name: "FileRepoConnectRepository", header: "Connect HTTP/REST repository …", - help: "Connects to an online repository via HTTP/REST.", - args: new AasxMenuListOfArgDefs() - .Add("Endpoint", "Endpoint of repo (without \"/server/listaas\").")) - .AddWpfBlazor(name: "FileRepoConnectRegistry", header: "Query HTTP/REST registry …") - .AddSeparator() - .AddWpfBlazor(name: "FileRepoCreateLRU", header: "Create last recently used list …") .AddSeparator() - .AddWpfBlazor(name: "FileRepoQuery", header: "Query open repositories …", inputGesture: "F12", - help: "Selects and repository item (AASX) from the open AASX file repositories.", - args: new AasxMenuListOfArgDefs() - .Add("Index", "Zero-based integer index to the list of all open repos.") - .Add("AAS", "String with AAS-Id") - .Add("Asset", "String with Asset-Id."))) + .AddWpfBlazor(name: "FileRepoCreateLRU", header: "Create last recently used list …")) .AddSeparator() .AddMenu(header: "Import …", attachPoint: "import", childs: (new AasxMenu()) .AddWpfBlazor(name: "ImportAASX", header: "Import further AASX file into AASX …", diff --git a/src/AasxPackageLogic/IMainWindow.cs b/src/AasxPackageLogic/IMainWindow.cs index 86031766e..add90a057 100644 --- a/src/AasxPackageLogic/IMainWindow.cs +++ b/src/AasxPackageLogic/IMainWindow.cs @@ -15,9 +15,11 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxPackageLogic; using AasxPackageLogic.PackageCentral; using AdminShellNS; +using Aas = AasCore.Aas3_0; using AnyUi; using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; namespace AasxPackageExplorer { @@ -135,6 +137,10 @@ void UiLoadPackageWithNew( bool preserveEditMode = false, bool? nextEditMode = null); + public Task UiSearchRepoAndExtendEnvironmentAsync( + AdminShellPackageEnvBase packEnv, + Aas.IReference workRef); + /// /// Check for menu switch and flush events, if required. /// diff --git a/src/AasxPackageLogic/MainWindowHeadless.cs b/src/AasxPackageLogic/MainWindowHeadless.cs index 0ea8114d7..cb585ed47 100644 --- a/src/AasxPackageLogic/MainWindowHeadless.cs +++ b/src/AasxPackageLogic/MainWindowHeadless.cs @@ -1561,14 +1561,6 @@ record = o; { ticket.StartExec(); - // access - if (PackageCentral.Repositories == null || PackageCentral.Repositories.Count < 1) - { - LogErrorToTicket(ticket, - "AASX File Repository: No repository currently available! Please open."); - return; - } - // make a lambda Action lambda = (ri) => { @@ -1601,7 +1593,8 @@ record = o; }; // get the list of items - var repoItems = PackageCentral.Repositories.EnumerateItems().ToList(); + var repoItems = PackageCentral.Repositories?.EnumerateItems().ToList() + ?? new List(); // scripted? if (ticket["Index"] is int) @@ -1650,7 +1643,20 @@ record = o; uc.Items = repoItems; if (DisplayContext.StartFlyoverModal(uc)) { - lambda(uc.ResultItem); + // try the main window for remote repos + if (uc.ResultId != null) + { + var rf = new Aas.Reference(ReferenceTypes.ExternalReference, + (new Aas.IKey[] { new Aas.Key(KeyTypes.GlobalReference, uc.ResultId) }).ToList()); + + var rres = await MainWindow?.UiSearchRepoAndExtendEnvironmentAsync(PackageCentral.Main, rf); + if (rres != null) + return; + } + + // got an file repo item? + if (uc.ResultItem != null) + lambda(uc.ResultItem); } } } diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index 641120989..46b57f3c8 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -58,7 +58,8 @@ public class AasIdentifiableSideInfo : OnDemandSideInfoBase /// This class provides some service functions to manage on demand list of Identifiables. ///
/// - public class OnDemandListIdentifiable : OnDemandList where T : Aas.IIdentifiable + public class OnDemandListIdentifiable : OnDemandList, IClass + where T : Aas.IIdentifiable { public int FindSideInfoIndexFromId(string id) { @@ -96,6 +97,44 @@ public static AasIdentifiableSideInfo FindSideInfoInListOfIdentifiables( } return null; } + + // + // make this class suitable for IClass as well + // + + IEnumerable IClass.DescendOnce() + { + foreach (var it in this) + yield return it; + } + + IEnumerable IClass.Descend() + { + foreach (var it in this) + { + yield return it; + foreach (var i2 in it.Descend()) + yield return i2; + } + } + + void IClass.Accept(Visitation.IVisitor visitor) + { + } + + void IClass.Accept(Visitation.IVisitorWithContext visitor, TContext context) + { + } + + T1 IClass.Transform(Visitation.ITransformer transformer) + { + return default(T1); + } + + T1 IClass.Transform(Visitation.ITransformerWithContext transformer, TContext context) + { + return default(T1); + } } /// diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index c1b85c545..5a91111fd 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -360,40 +360,27 @@ public void AddThumbnail(string aasId, byte[] content) } } - public byte[] GetThumbnail(string aasId) + public override byte[] GetThumbnailBytesFromAasOrPackage(string aasId) { - if (aasId?.HasContent() != true || !_thumbStreamPerAasId.ContainsKey(aasId)) - return null; - return _thumbStreamPerAasId[aasId]; - } - - public override Stream GetThumbnailStreamFromAasOrPackage(string aasId) - { - // check for content - var content = GetThumbnail(aasId); - if (content == null) - return null; + // can serve ourself? + if (aasId?.HasContent() == true && _thumbStreamPerAasId.ContainsKey(aasId)) + return _thumbStreamPerAasId[aasId]; - // return stream - var ms = new MemoryStream(); - ms.Write(content, 0, content.Length); - ms.Seek(0, SeekOrigin.Begin); - return ms; + // refer to base + return base.GetThumbnailBytesFromAasOrPackage(aasId); } - public override async Task GetLocalStreamFromPackageAsync( + public override async Task GetBytesFromPackageOrExternalAsync( string uriString, string aasId = null, string smId = null, - string idShortPath = null, - FileMode mode = FileMode.Open, - FileAccess access = FileAccess.Read) + string idShortPath = null) { // IMPORTANT! First try to use the base implementation to get an stream to // HTTP or ABSOLUTE file - var absStream = base.GetLocalStreamFromPackage(uriString, mode: mode, access: access); - if (absStream != null) - return absStream; + var absBytes = await base.GetBytesFromPackageOrExternalAsync(uriString); + if (absBytes != null) + return absBytes; // ok, try to load from the server if (aasId?.HasContent() != true || idShortPath?.HasContent() != true @@ -410,7 +397,7 @@ public override async Task GetLocalStreamFromPackageAsync( try { - Stream res = null; + byte[] res = null; await PackageHttpDownloadUtil.HttpGetToMemoryStream( null, @@ -423,7 +410,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( return; // store (this is stupid!) - res = new MemoryStream(ms.ToByteArray()); + res = ms.ToArray(); }); return res; diff --git a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs index 4808c3ddf..0d5ab837b 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs @@ -418,6 +418,22 @@ public IEnumerable FindAllReferables() where T : class, Aas.IReferable } } + // + // Determine full item locations from file repositories, but also connected repositories + // + + //public enum FindIdKind { Asset, Aas } + + //public string FindFullItemLocationForId( + // FindIdKind kind, + // string id, + // Action lambdaFoundContainer) + //{ + // // access + // if (id?.HasContent() != true) + // return null; + //} + // // Event management // diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 365972b6c..542480132 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -71,6 +71,7 @@ public override string Location set { SetNewLocation(value); OnPropertyChanged("InfoLocation"); } } + [JsonIgnore] public AdminShellPackageDynamicFetchEnv EnvDynPack { get => Env as AdminShellPackageDynamicFetchEnv; } // @@ -585,7 +586,8 @@ private static async Task FromRegistryGetAasAndSubmodels( PackCntRuntimeOptions runtimeOptions, bool allowFakeResponses, dynamic aasDescriptor, - List trackNewIdentifiables = null) + List trackNewIdentifiables = null, + List trackLoadedIdentifiables = null) { // access if (record == null) @@ -675,6 +677,7 @@ private static async Task FromRegistryGetAasAndSubmodels( (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, smrr.Id) }).ToList())); // add this AAS + trackLoadedIdentifiables?.Add(aas); if (prepAas?.AddIfNew(aas, aasSi) == true) trackNewIdentifiables?.Add(aas); @@ -704,6 +707,7 @@ private static async Task FromRegistryGetAasAndSubmodels( // no, add with data si.IsStub = false; + trackLoadedIdentifiables?.Add(sm); if (prepSM?.AddIfNew(sm, si) == true) trackNewIdentifiables?.Add(sm); }); @@ -756,6 +760,7 @@ public static async Task LoadFromSourceInternalAsync( AdminShellPackageEnvBase targetEnv = null, bool loadNew = true, List trackNewIdentifiables = null, + List trackLoadedIdentifiables = null, PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) { @@ -854,7 +859,7 @@ public static async Task LoadFromSourceInternalAsync( // refer to dedicated function await FromRegistryGetAasAndSubmodels( prepAas, prepSM, record, runtimeOptions, allowFakeResponses, singleDesc, - trackNewIdentifiables); + trackNewIdentifiables, trackLoadedIdentifiables); } if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) @@ -864,10 +869,11 @@ await FromRegistryGetAasAndSubmodels( BuildUriForRepoSingleAAS(baseUri, id, encryptIds: true), runtimeOptions, allowFakeResponses); - // foudn? + // found? if (aas != null) { // add + trackLoadedIdentifiables?.Add(aas); if (prepAas.AddIfNew(aas, new AasIdentifiableSideInfo() { IsStub = false, @@ -920,7 +926,7 @@ await FromRegistryGetAasAndSubmodels( // refer to dedicated function await FromRegistryGetAasAndSubmodels( prepAas, prepSM, record, runtimeOptions, allowFakeResponses, res, - trackNewIdentifiables); + trackNewIdentifiables, trackLoadedIdentifiables); } } @@ -970,7 +976,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // refer to dedicated function var res = await FromRegistryGetAasAndSubmodels( prepAas, prepSM, record, runtimeOptions, allowFakeResponses, aasDesc, - trackNewIdentifiables); + trackNewIdentifiables, trackLoadedIdentifiables); if (!res) { runtimeOptions?.Log?.Error("Error retrieving AAS from registry! Aborting."); @@ -1062,6 +1068,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( added = prepSM.AddIfNew( idf as Aas.ISubmodel, si); + trackLoadedIdentifiables?.Add(idf); if (added) trackNewIdentifiables?.Add(idf); } @@ -1105,6 +1112,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var aas = Jsonization.Deserialize.AssetAdministrationShellFrom(node); + trackLoadedIdentifiables?.Add(aas); if (prepAas.AddIfNew(aas, new AasIdentifiableSideInfo() { IsStub = false, @@ -1144,6 +1152,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var sm = Jsonization.Deserialize.SubmodelFrom(node); + trackLoadedIdentifiables?.Add(sm); if (prepSM.AddIfNew(sm, new AasIdentifiableSideInfo() { IsStub = false, @@ -1183,6 +1192,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var cd = Jsonization.Deserialize.ConceptDescriptionFrom(node); + trackLoadedIdentifiables?.Add(cd); if (prepCD.AddIfNew(cd, new AasIdentifiableSideInfo() { IsStub = false, @@ -1335,6 +1345,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var node = System.Text.Json.Nodes.JsonNode.Parse(ms); var sm = Jsonization.Deserialize.SubmodelFrom(node); if (fi.Type == FetchItemType.SmUrl || fi.Type == FetchItemType.SmId) + { + trackLoadedIdentifiables?.Add(sm); if (prepSM.AddIfNew(sm, new AasIdentifiableSideInfo() { IsStub = false, @@ -1345,7 +1357,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( })) { trackNewIdentifiables?.Add(sm); - } + } + } } catch (Exception ex) { @@ -1409,6 +1422,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var sm = Jsonization.Deserialize.SubmodelFrom(node); lock (prepSM) { + trackLoadedIdentifiables?.Add(sm); if (prepSM.AddIfNew(sm, new AasIdentifiableSideInfo() { IsStub = false, @@ -1473,22 +1487,47 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( return null; } - // bring back to env - env.AssetAdministrationShells = prepAas; - env.Submodels = prepSM; - env.ConceptDescriptions = prepCD; - - // remove, what is not need - if (env.AssetAdministrationShellCount() < 1) - env.AssetAdministrationShells = null; - if (env.SubmodelCount() < 1) - env.Submodels = null; - if (env.ConceptDescriptionCount() < 1) - env.ConceptDescriptions = null; - - // commit - targetEnv = dynPack; - targetEnv.SetEnvironment(env); + // how to commit? + if (loadNew) + { + // commit new situation + // bring back to env + env.AssetAdministrationShells = prepAas; + env.Submodels = prepSM; + env.ConceptDescriptions = prepCD; + + // remove, what is not need + if (env.AssetAdministrationShellCount() < 1) + env.AssetAdministrationShells = null; + if (env.SubmodelCount() < 1) + env.Submodels = null; + if (env.ConceptDescriptionCount() < 1) + env.ConceptDescriptions = null; + + // commit + targetEnv = dynPack; + targetEnv.SetEnvironment(env); + } + else + { + // for the "not new" option, treat the existing situation very carefully + if (prepAas.Count() < 1) + prepAas = null; + if (prepSM.Count() < 1) + prepSM = null; + if (prepCD.Count() < 1) + prepCD = null; + + // adopt back + if (env.AssetAdministrationShells == null && prepAas != null) + env.AssetAdministrationShells = prepAas; + if (env.Submodels == null && prepSM != null) + env.Submodels = prepSM; + if (env.ConceptDescriptions == null && prepCD != null) + env.ConceptDescriptions = prepCD; + } + + // for the records (targetEnv as AdminShellPackageDynamicFetchEnv)?.SetContext(new PackageContainerHttpRepoSubsetFetchContext() { Record = record, @@ -1698,9 +1737,12 @@ public PackageContainerHttpRepoSubsetOptions( PackageContainerOptionsBase baseOpt, ConnectExtendedRecord record) { - LoadResident = baseOpt.LoadResident; - StayConnected = baseOpt.StayConnected; - UpdatePeriod = baseOpt.UpdatePeriod; + if (baseOpt != null) + { + LoadResident = baseOpt.LoadResident; + StayConnected = baseOpt.StayConnected; + UpdatePeriod = baseOpt.UpdatePeriod; + } if (baseOpt is PackageContainerHttpRepoSubsetOptions fullOpt) Record = fullOpt.Record?.Copy(); @@ -1817,11 +1859,22 @@ public static string BuildLocationFrom( return null; } + public enum ConnectExtendedScope { + All = 0xffff, + BaseInfo = 0x0001, + IdfTypes = 0x0002, + Query = 0x004, + GetOptions = 0x0008, + StayConnected = 0x0010, + Pagination = 0x0020 + } + public static async Task PerformConnectExtendedDialogue( AasxMenuActionTicket ticket, AnyUiContextBase displayContext, string caption, - ConnectExtendedRecord record) + ConnectExtendedRecord record, + ConnectExtendedScope scope = ConnectExtendedScope.All) { // access if (displayContext == null || caption?.HasContent() != true || record == null) @@ -1890,358 +1943,381 @@ public static async Task PerformConnectExtendedDialogue( // dynamic rows int row = 0; - // Base address + Type - helper.AddSmallLabelTo(g, row, 0, content: "Base address:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + if ((scope & ConnectExtendedScope.BaseInfo) > 0) + { + // Base address + Type + helper.AddSmallLabelTo(g, row, 0, content: "Base address:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - var g2 = helper.AddSmallGridTo(g, row, 1, 1, 2, new[] { "#", "*" }); + var g2 = helper.AddSmallGridTo(g, row, 1, 1, 2, new[] { "#", "*" }); - AnyUiUIElement.SetIntFromControl( - helper.Set( - helper.AddSmallComboBoxTo(g2, 0, 0, - items: ConnectExtendedRecord.BaseTypeEnumNames, - selectedIndex: (int)record.BaseType, - margin: new AnyUiThickness(0, 0, 5, 0), - padding: new AnyUiThickness(0, 0, 0, 0)), - minWidth: 200, maxWidth: 200), - (i) => { record.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 0, + items: ConnectExtendedRecord.BaseTypeEnumNames, + selectedIndex: (int)record.BaseType, + margin: new AnyUiThickness(0, 0, 5, 0), + padding: new AnyUiThickness(0, 0, 0, 0)), + minWidth: 200, maxWidth: 200), + (i) => { record.BaseType = (ConnectExtendedRecord.BaseTypeEnum)i; }); + + if (displayContext is AnyUiContextPlusDialogs cpd + && cpd.HasCapability(AnyUiContextCapability.WPF)) + { + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 1, + isEditable: true, + items: Options.Curr.BaseAddresses?.ToArray(), + text: "" + record.BaseAddress, + margin: new AnyUiThickness(0, 0, 0, 0), + padding: new AnyUiThickness(0, 0, 0, 0), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), + (s) => { record.BaseAddress = s; }); + } + else + { + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g2, 0, 1, + text: $"{record.BaseAddress}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.BaseAddress = s; }); + } - if (displayContext is AnyUiContextPlusDialogs cpd - && cpd.HasCapability(AnyUiContextCapability.WPF)) - { - AnyUiUIElement.SetStringFromControl( - helper.Set( - helper.AddSmallComboBoxTo(g2, 0, 1, - isEditable: true, - items: Options.Curr.BaseAddresses?.ToArray(), - text: "" + record.BaseAddress, - margin: new AnyUiThickness(0, 0, 0, 0), - padding: new AnyUiThickness(0, 0, 0, 0), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch)), - (s) => { record.BaseAddress = s; }); + row++; } - else + + if ((scope & ConnectExtendedScope.IdfTypes) > 0) { - AnyUiUIElement.SetStringFromControl( + // All AASes + AnyUiUIElement.RegisterControl( helper.Set( - helper.AddSmallTextBoxTo(g2, 0, 1, - text: $"{record.BaseAddress}", - verticalAlignment: AnyUiVerticalAlignment.Center, + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get all AAS", + isChecked: record.GetAllAas, verticalContentAlignment: AnyUiVerticalAlignment.Center), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { record.BaseAddress = s; }); - } - - row++; - - // All AASes - AnyUiUIElement.RegisterControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 0, - content: "Get all AAS", - isChecked: record.GetAllAas, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - colSpan: 2), - (o) => { - if ((bool) o) - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllAas); - else - record.GetAllAas = false; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); - row++; + colSpan: 2), + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllAas); + else + record.GetAllAas = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + row++; - // Single AAS - AnyUiUIElement.RegisterControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 0, - content: "Get single AAS", - isChecked: record.GetSingleAas, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - colSpan: 2), - (o) => { - if ((bool)o) - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleAas); - else - record.GetSingleAas = false; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); + // Single AAS + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get single AAS", + isChecked: record.GetSingleAas, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleAas); + else + record.GetSingleAas = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); - helper.AddSmallLabelTo(g, row + 1, 0, content: "AAS.Id:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + helper.AddSmallLabelTo(g, row + 1, 0, content: "AAS.Id:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - AnyUiUIElement.SetStringFromControl( - helper.Set( - helper.AddSmallTextBoxTo(g, row + 1, 1, - text: $"{record.AasId}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { record.AasId = s; }); + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.AasId}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.AasId = s; }); - row += 2; + row += 2; - // AAS(es) by asset link - AnyUiUIElement.RegisterControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 0, - content: "AAS by AssetId", - isChecked: record.GetAasByAssetLink, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - colSpan: 2), - (o) => { - if ((bool)o) - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AasByAssetLink); - else - record.GetAasByAssetLink = false; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); + // AAS(es) by asset link + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "AAS by AssetId", + isChecked: record.GetAasByAssetLink, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AasByAssetLink); + else + record.GetAasByAssetLink = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); - helper.AddSmallLabelTo(g, row + 1, 0, content: "AssetId:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + helper.AddSmallLabelTo(g, row + 1, 0, content: "AssetId:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - AnyUiUIElement.SetStringFromControl( - helper.Set( - helper.AddSmallTextBoxTo(g, row + 1, 1, - text: $"{record.AssetId}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { record.AssetId = s; }); + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.AssetId}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.AssetId = s; }); - row += 2; + row += 2; - // All Submodels - AnyUiUIElement.RegisterControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 0, - content: "Get all Submodels", - isChecked: record.GetAllSubmodel, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - colSpan: 2), - (o) => { - if ((bool)o) - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllSM); - else - record.GetAllSubmodel = false; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); - row++; + // All Submodels + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get all Submodels", + isChecked: record.GetAllSubmodel, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllSM); + else + record.GetAllSubmodel = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + row++; - // Single Submodel - AnyUiUIElement.RegisterControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 0, - content: "Get single Submodel", - isChecked: record.GetSingleSubmodel, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - colSpan: 2), - (o) => { - if ((bool)o) - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleSM); - else - record.GetSingleSubmodel = false; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); + // Single Submodel + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get single Submodel", + isChecked: record.GetSingleSubmodel, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleSM); + else + record.GetSingleSubmodel = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); - helper.AddSmallLabelTo(g, row + 1, 0, content: "Submodel.Id:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + helper.AddSmallLabelTo(g, row + 1, 0, content: "Submodel.Id:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - AnyUiUIElement.SetStringFromControl( - helper.Set( - helper.AddSmallTextBoxTo(g, row + 1, 1, - text: $"{record.SmId}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { record.SmId = s; }); + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.SmId}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.SmId = s; }); - row += 2; + row += 2; - // Single CD - AnyUiUIElement.RegisterControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 0, - content: "Get single ConceptDescription (CD)", - isChecked: record.GetSingleCD, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - colSpan: 2), - (o) => { - if ((bool)o) - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleCD); - else - record.GetSingleCD = false; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); + // Single CD + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get single ConceptDescription (CD)", + isChecked: record.GetSingleCD, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleCD); + else + record.GetSingleCD = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); - helper.AddSmallLabelTo(g, row + 1, 0, content: "CD.Id:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + helper.AddSmallLabelTo(g, row + 1, 0, content: "CD.Id:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - AnyUiUIElement.SetStringFromControl( - helper.Set( - helper.AddSmallTextBoxTo(g, row + 1, 1, - text: $"{record.CdId}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch), - (s) => { record.CdId = s; }); + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.CdId}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + (s) => { record.CdId = s; }); - row += 2; + row += 2; + } - // Query - AnyUiUIElement.RegisterControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 0, - content: "Get by query definition", - isChecked: record.ExecuteQuery, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - colSpan: 2), - (o) => { - if ((bool)o) - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.Query); - else - record.ExecuteQuery = false; - return new AnyUiLambdaActionModalPanelReRender(uc); - }); + if ((scope & ConnectExtendedScope.Query) > 0) + { + // Query + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get by query definition", + isChecked: record.ExecuteQuery, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.Query); + else + record.ExecuteQuery = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); - helper.AddSmallLabelTo(g, row + 1, 0, content: "Query:", - verticalAlignment: AnyUiVerticalAlignment.Top, - verticalContentAlignment: AnyUiVerticalAlignment.Top); + helper.AddSmallLabelTo(g, row + 1, 0, content: "Query:", + verticalAlignment: AnyUiVerticalAlignment.Top, + verticalContentAlignment: AnyUiVerticalAlignment.Top); - AnyUiUIElement.SetStringFromControl( - helper.Set( - helper.AddSmallTextBoxTo(g, row + 1, 1, - text: $"{record.QueryScript}", - verticalAlignment: AnyUiVerticalAlignment.Stretch, - verticalContentAlignment: AnyUiVerticalAlignment.Top, - textWrap: AnyUiTextWrapping.Wrap, - fontSize: 0.7, - multiLine: true), - horizontalAlignment: AnyUiHorizontalAlignment.Stretch, - minHeight: 120), - (s) => { record.QueryScript = s; }); - - row += 2; - - // Auto load Submodels - - helper.AddSmallLabelTo(g, row, 0, content: "For above:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g, row + 1, 1, + text: $"{record.QueryScript}", + verticalAlignment: AnyUiVerticalAlignment.Stretch, + verticalContentAlignment: AnyUiVerticalAlignment.Top, + textWrap: AnyUiTextWrapping.Wrap, + fontSize: 0.7, + multiLine: true), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch, + minHeight: 120), + (s) => { record.QueryScript = s; }); + + row += 2; + } - AnyUiUIElement.SetBoolFromControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 1, - content: "Auto-load Submodels", - isChecked: record.AutoLoadSubmodels, - verticalContentAlignment: AnyUiVerticalAlignment.Center)), - (b) => { record.AutoLoadSubmodels = b; }); + if ((scope & ConnectExtendedScope.GetOptions) > 0) + { + // Auto load Submodels + helper.AddSmallLabelTo(g, row, 0, content: "For above:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - row++; + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Auto-load Submodels", + isChecked: record.AutoLoadSubmodels, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.AutoLoadSubmodels = b; }); - // Auto load Submodels + row++; - AnyUiUIElement.SetBoolFromControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 1, - content: "Auto-load ConceptDescriptions", - isChecked: record.AutoLoadCds, - verticalContentAlignment: AnyUiVerticalAlignment.Center)), - (b) => { record.AutoLoadCds = b; }); + // Auto load Submodels - row++; + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Auto-load ConceptDescriptions", + isChecked: record.AutoLoadCds, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.AutoLoadCds = b; }); - // Auto load Thumbnails + row++; - AnyUiUIElement.SetBoolFromControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 1, - content: "Auto-load thumbnail for every AAS", - isChecked: record.AutoLoadThumbnails, - verticalContentAlignment: AnyUiVerticalAlignment.Center)), - (b) => { record.AutoLoadThumbnails = b; }); + // Auto load Thumbnails - row++; + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Auto-load thumbnail for every AAS", + isChecked: record.AutoLoadThumbnails, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.AutoLoadThumbnails = b; }); - // Auto load on demand + row++; - AnyUiUIElement.SetBoolFromControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 1, - content: "Mark auto-loaded elements for on-demand loading", - isChecked: record.AutoLoadOnDemand, - verticalContentAlignment: AnyUiVerticalAlignment.Center)), - (b) => { record.AutoLoadOnDemand = b; }); + // Auto load on demand - row++; + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Mark auto-loaded elements for on-demand loading", + isChecked: record.AutoLoadOnDemand, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.AutoLoadOnDemand = b; }); - // Encrypt IDs + row++; - AnyUiUIElement.SetBoolFromControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 1, - content: "Encrypt Ids (needs to be checked, unless encrypted Ids are provided)", - isChecked: record.EncryptIds, - verticalContentAlignment: AnyUiVerticalAlignment.Center)), - (b) => { record.EncryptIds = b; }); + // Encrypt IDs - row++; + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Encrypt Ids (needs to be checked, unless encrypted Ids are provided)", + isChecked: record.EncryptIds, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.EncryptIds = b; }); - // Stay connected + row++; + } - AnyUiUIElement.SetBoolFromControl( - helper.Set( - helper.AddSmallCheckBoxTo(g, row, 1, - content: "Stay connected (will receive events)", - isChecked: record.StayConnected, - verticalContentAlignment: AnyUiVerticalAlignment.Center)), - (b) => { record.StayConnected = b; }); + if ((scope & ConnectExtendedScope.StayConnected) > 0) + { + // Stay connected + AnyUiUIElement.SetBoolFromControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 1, + content: "Stay connected (will receive events)", + isChecked: record.StayConnected, + verticalContentAlignment: AnyUiVerticalAlignment.Center)), + (b) => { record.StayConnected = b; }); + + row++; + } - row++; + if ((scope & ConnectExtendedScope.Pagination) > 0) + { + // Pagination + helper.AddSmallLabelTo(g, row, 0, content: "Pagination:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - // Pagination - helper.AddSmallLabelTo(g, row, 0, content: "Pagination:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + var g3 = helper.AddSmallGridTo(g, row, 1, 1, 4, new[] { "#", "*", "#", "*" }); - var g3 = helper.AddSmallGridTo(g, row, 1, 1, 4, new[] { "#", "*", "#", "*" }); + helper.AddSmallLabelTo(g3, 0, 0, content: "Limit results:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - helper.AddSmallLabelTo(g3, 0, 0, content: "Limit results:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g3, 0, 1, + margin: new AnyUiThickness(10, 0, 0, 0), + text: $"{record.PageLimit:D}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + minWidth: 80, maxWidth: 80, + horizontalAlignment: AnyUiHorizontalAlignment.Left), + (i) => { record.PageLimit = i; }); - AnyUiUIElement.SetIntFromControl( - helper.Set( - helper.AddSmallTextBoxTo(g3, 0, 1, - margin: new AnyUiThickness(10, 0, 0, 0), - text: $"{record.PageLimit:D}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - minWidth: 80, maxWidth: 80, - horizontalAlignment: AnyUiHorizontalAlignment.Left), - (i) => { record.PageLimit = i; }); - - helper.AddSmallLabelTo(g3, 0, 2, content: "Skip results:", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center); + helper.AddSmallLabelTo(g3, 0, 2, content: "Skip results:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); - AnyUiUIElement.SetIntFromControl( - helper.Set( - helper.AddSmallTextBoxTo(g3, 0, 3, - margin: new AnyUiThickness(10, 0, 0, 0), - text: $"{record.PageSkip:D}", - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center), - minWidth: 80, maxWidth: 80, - horizontalAlignment: AnyUiHorizontalAlignment.Left), - (i) => { record.PageSkip = i; }); + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g3, 0, 3, + margin: new AnyUiThickness(10, 0, 0, 0), + text: $"{record.PageSkip:D}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + minWidth: 80, maxWidth: 80, + horizontalAlignment: AnyUiHorizontalAlignment.Left), + (i) => { record.PageSkip = i; }); - row++; + row++; + } // give back return g; @@ -2794,7 +2870,7 @@ public static async Task PerformUploadAssistant( continue; // try read the bytes (should have try/catch in it) - var ba = await packEnv.GetByteArrayFromExternalInternalUri(filEl.FileSme.Value); + var ba = await packEnv.GetBytesFromPackageOrExternalAsync(filEl.FileSme.Value); if (ba == null || ba.Length < 1) { Log.Singleton.Error("Centralize file: cannot read file: {0}", filEl.FileSme.Value); diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs index dccbb53c1..f4525dd1a 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs @@ -175,7 +175,7 @@ protected JsonSerializerSettings GetSerializerSettings() // need special settings (to handle different typs of child classes of PackageContainer) var settings = AasxPluginOptionSerialization.GetDefaultJsonSettings( new[] { typeof(PackageContainerListBase), typeof(PackageContainerLocalFile), - typeof(PackageContainerNetworkHttpFile) }); + typeof(PackageContainerNetworkHttpFile), typeof(PackageContainerHttpRepoSubset) }); return settings; } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs index 0fc479687..e2124da88 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs @@ -35,13 +35,12 @@ public class PackageContainerListHttpRestRepository : PackageContainerListHttpRe private PackageConnectorHttpRest _connector; /// - /// REST endpoint of the AAS repository, that is, without /server/listaas + /// REST endpoint of the AAS repository, that is, without /shells etc. but + /// with e.g. /api/v3.0/ /// [JsonIgnore] public Uri Endpoint; - public bool IsAspNetConnection { get; set; } - // // Constructor // @@ -52,9 +51,8 @@ public PackageContainerListHttpRestRepository(string location) Endpoint = new Uri(location); // directly set endpoint - _connector = new PackageConnectorHttpRest(null, Endpoint); - - + // Note: later + // _connector = new PackageConnectorHttpRest(null, Endpoint); } // @@ -73,6 +71,10 @@ public async Task SyncronizeFromServerAsync() { if (true != _connector?.IsValid()) return false; + + await Task.Yield(); + +#if old_implementation // try get a list of items from the connector var items = await _connector.GenerateRepositoryFromEndpointAsync(); // just re-set @@ -83,6 +85,7 @@ public async Task SyncronizeFromServerAsync() FileMap.Add(fi); fi.ContainerList = this; } +#endif // ok return true; diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerRepoItem.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerRepoItem.cs index 713012b5c..21f098d2a 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerRepoItem.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerRepoItem.cs @@ -7,8 +7,10 @@ This source code is licensed under the Apache License 2.0 (see LICENSE.txt). This source code may use other Open Source software components (see LICENSE.txt). */ -using AasxIntegrationBase; using AdminShellNS; +using Extensions; +using Aas = AasCore.Aas3_0; +using AasxIntegrationBase; using AnyUi; using Newtonsoft.Json; using System; @@ -421,27 +423,49 @@ public void CleanIds() /// re-calculates the particulare lists of ids. If the tag and/ or description is empty, /// it will also build a generated tag or descriptions /// - public void CalculateIdsTagAndDesc(bool force = false) + public void CalculateIdsTagAndDesc( + Aas.IAssetAdministrationShell specificAas = null, + bool force = false) { // Ids CleanIds(); - Env?.AasEnv?.AssetAdministrationShells?.ForEach((x) => + if (specificAas != null) { - if (true == x?.Id.HasContent()) - _aasIds.Add(x.Id); - }); + _aasIds.Add(specificAas.Id); + + if (specificAas?.AssetInformation?.GlobalAssetId?.HasContent() == true) + _assetIds.Add(specificAas.AssetInformation.GlobalAssetId); - Env?.AasEnv?.Submodels?.ForEach((x) => + foreach (var x in specificAas.FindAllSubmodelReferences()) + if (x.Reference?.IsValid() == true && x.Reference.Keys[0].Type == KeyTypes.Submodel) + _submodelIds.Add(x.Reference.Keys[0].Value); + } + else { - if (true == x?.Id.HasContent()) - _submodelIds.Add(x.Id); - }); + // take the whole environment + Env?.AasEnv?.AssetAdministrationShells?.ForEach((x) => + { + if (true == x?.Id.HasContent()) + _aasIds.Add(x.Id); + + if (x?.AssetInformation?.GlobalAssetId?.HasContent() == true) + _assetIds.Add(x.AssetInformation.GlobalAssetId); + }); + + Env?.AasEnv?.Submodels?.ForEach((x) => + { + if (true == x?.Id.HasContent()) + _submodelIds.Add(x.Id); + }); + } - // get some descriptiive data + // get some descriptive data var threeFn = Path.GetFileNameWithoutExtension(Location); var aas0 = Env?.AasEnv?.AssetAdministrationShells?.FirstOrDefault(); + if (specificAas != null) + aas0 = specificAas; // Tag if (!Tag.HasContent() || force) diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 91267358d..e741dd98c 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -37,6 +37,7 @@ namespace AasxPackageLogic public interface IManageVisualAasxElements { VisualElementGeneric GetSelectedItem(); + ListOfVisualElementBasic GetSelectedItems(); } public interface ITaintableIdentifiable diff --git a/src/AasxPluginExportTable/Smt/ExportSmt.cs b/src/AasxPluginExportTable/Smt/ExportSmt.cs index 34c6b243e..9e8efc0c3 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmt.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmt.cs @@ -105,7 +105,7 @@ protected void ProcessImageLink(Aas.ISubmodelElement sme) string dataExt = ".bin"; if (sme is Aas.IFile smeFile) { - data = _package?.GetByteArrayFromUriOrLocalPackage(smeFile.Value); + data = _package?.GetBytesFromPackageOrExternal(smeFile.Value); dataExt = Path.GetExtension(smeFile.Value); } diff --git a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs index 1bc4a1486..3a86ac5d6 100644 --- a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs +++ b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs @@ -397,7 +397,7 @@ protected void SetBasicInfos() { try { - return await _package.GetLocalStreamFromPackageAsync( + return await _package.GetBytesFromPackageOrExternalAsync( uriString: fe.Value, aasId: "" + aas?.Id, smId: "" + _submodel.Id, @@ -409,10 +409,10 @@ protected void SetBasicInfos() return null; }); task.Wait(); - var stream = task.Result; + var imgBytes = task.Result; // convert to image - bi = AnyUiGdiHelper.LoadBitmapInfoFromStream(stream); + bi = AnyUiGdiHelper.LoadBitmapInfoFromBytes(imgBytes); } // BLOB diff --git a/src/AasxWpfControlLibrary/Flyouts/SelectFromRepositoryFlyout.xaml.cs b/src/AasxWpfControlLibrary/Flyouts/SelectFromRepositoryFlyout.xaml.cs index eb1083a7b..ce43697ac 100644 --- a/src/AasxWpfControlLibrary/Flyouts/SelectFromRepositoryFlyout.xaml.cs +++ b/src/AasxWpfControlLibrary/Flyouts/SelectFromRepositoryFlyout.xaml.cs @@ -26,6 +26,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxPackageLogic; using AasxPackageLogic.PackageCentral; using AasxWpfControlLibrary.PackageCentral; +using AdminShellNS; using AnyUi; using Newtonsoft.Json; @@ -134,11 +135,23 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e) private void ButtonOk_Click(object sender, RoutedEventArgs e) { // search - var ri = DiaData?.SearchId(TextBoxAssetId.Text); + var assid = TextBoxAssetId.Text; + var ri = DiaData?.SearchId(assid); if (ri != null) { DiaData.Result = true; DiaData.ResultItem = ri; + DiaData.ResultId = assid; + ControlClosed?.Invoke(); + return; + } + + // use the asset id only + if (assid.HasContent()) + { + DiaData.Result = true; + DiaData.ResultItem = null; + DiaData.ResultId = assid; ControlClosed?.Invoke(); return; } diff --git a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs index 575f86d3a..379e61b4d 100644 --- a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs +++ b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs @@ -14,6 +14,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxPackageLogic.PackageCentral; using AasxPackageLogic.PackageCentral.AasxFileServerInterface; using AdminShellNS; +using Aas = AasCore.Aas3_0; using AnyUi; using System; using System.IO; @@ -22,6 +23,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using static QRCoder.PayloadGenerator; namespace AasxWpfControlLibrary.PackageCentral { @@ -273,16 +275,14 @@ public async Task CommandBinding_FileRepoAll(Control senderList, PackageContaine if (cmd == "filerepoaddcurrent") { - // check - var veAas = _manageVisuElems?.GetSelectedItem() as VisualElementAdminShell; - - var veEnv = veAas?.FindFirstParent((ve) => - (ve is VisualElementEnvironmentItem vev - && vev.theItemType == VisualElementEnvironmentItem.ItemType.Package), includeThis: false) - as VisualElementEnvironmentItem; - - if (veAas == null || veAas.theAas == null || veAas.theEnv == null || veAas.thePackage == null - || veEnv == null || !veEnv.thePackageSourceFn.HasContent()) + // check, if to get all infos + var veAass = _manageVisuElems?.GetSelectedItems()?.ToList(); + var veEnv = veAass.FirstOrDefault()?.FindFirstParent((ve) => + (ve is VisualElementEnvironmentItem vev + && vev.theItemType == VisualElementEnvironmentItem.ItemType.Package), includeThis: false) + as VisualElementEnvironmentItem; + + if (veAass == null || veAass.Count() < 1 || veEnv == null || !veEnv.thePackageSourceFn.HasContent()) { await _flyout?.GetDisplayContext()?.MessageBoxFlyoutShowAsync( "No valid AAS selected. The application needs to be in edit mode. " + @@ -291,16 +291,39 @@ public async Task CommandBinding_FileRepoAll(Control senderList, PackageContaine return; } - // generate appropriate container - var cnt = PackageContainerFactory.GuessAndCreateFor( - null, veEnv.thePackageSourceFn, veEnv.thePackageSourceFn, - overrideLoadResident: false, - containerOptions: PackageContainerOptionsBase.CreateDefault(Options.Curr)); - if (cnt is PackageContainerRepoItem ri) + // now, for each + foreach (var ve in veAass) { - ri.Env = veAas.thePackage; - ri.CalculateIdsTagAndDesc(); - fr.Add(ri); + // more specific access + if (ve is not VisualElementAdminShell veAas + || veAas.theAas == null || veAas.theEnv == null || veAas.thePackage == null + || veAas.theAas.Id?.HasContent() != true) + continue; + + // check for the location of the environment or more specifically + var location = veEnv.thePackageSourceFn; + if (veAas.theEnv?.AssetAdministrationShells is OnDemandListIdentifiable odli) + { + var ndx = odli.FindSideInfoIndexFromId(veAas.theAas.Id); + if (ndx >= 0) + { + var si = odli.GetSideInfo(ndx); + if (si.StubLevel >= AasIdentifiableSideInfoLevel.IdWithEndpoint) + location = si.Endpoint.ToString(); + } + } + + // generate appropriate container + var cnt = PackageContainerFactory.GuessAndCreateFor( + null, location, location, + overrideLoadResident: false, + containerOptions: PackageContainerOptionsBase.CreateDefault(Options.Curr)); + if (cnt is PackageContainerRepoItem ri) + { + ri.Env = veAas.thePackage; + ri.CalculateIdsTagAndDesc(specificAas: veAas.theAas); + fr.Add(ri); + } } } diff --git a/src/BlazorExplorer/Data/AasxInfoBox.cs b/src/BlazorExplorer/Data/AasxInfoBox.cs index a072a1149..6e544e250 100644 --- a/src/BlazorExplorer/Data/AasxInfoBox.cs +++ b/src/BlazorExplorer/Data/AasxInfoBox.cs @@ -67,66 +67,20 @@ public void SetInfos(Aas.IAssetAdministrationShell aas, AdminShellPackageEnvBase // image data? try { - if (env != null) + try { - System.IO.Stream s = null; - try - { - s = env.GetLocalThumbnailStream(); - } - catch - { - s = null; - } - if (s != null) - { - using (var m = new System.IO.MemoryStream()) - { - s.CopyTo(m); - HtmlImageData = System.Convert.ToBase64String(m.ToArray()); - } - - // it is indespensible to properly close the thumbnail stream! - // practice proofed not to use using .. - s.Close(); - } + var ba = env.GetThumbnailBytesFromAasOrPackage(aas?.Id); + HtmlImageData = System.Convert.ToBase64String(ba); + } + catch (Exception ex) + { + LogInternally.That.CompletelyIgnoredError(ex); } } catch (Exception ex) { AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); } - // dead-csharp off - // TODO (??, 0000-00-00): missng for WPF refactoring - // asset thumbnail - // try - // { - // // identify which stream to use.. - // if (_packageCentral.MainAvailable) - // try - // { - // using (var thumbStream = _packageCentral.Main.GetLocalThumbnailStream()) - // { - // // load image - // if (thumbStream != null) - // { - // var bi = new BitmapImage(); - // bi.BeginInit(); - - // // See https://stackoverflow.com/a/5346766/1600678 - // bi.CacheOption = BitmapCacheOption.OnLoad; - - // bi.StreamSource = thumbStream; - // bi.EndInit(); - // this.AssetPic.Source = bi; - // } - // } - // } - // catch (Exception ex) - // { - // AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); - // } - // dead-csharp on } } } diff --git a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs index 3acfe1715..58cf484d7 100644 --- a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs +++ b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs @@ -508,6 +508,17 @@ protected async Task LoadFromFileRepository(PackageC return null; } + public async Task UiSearchRepoAndExtendEnvironmentAsync( + AdminShellPackageEnvBase packEnv, + Aas.IReference workRef) + { + await Task.Yield(); + + // TODO: take over from WPF app + + return null; + } + public async Task UiHandleNavigateTo( Aas.IReference targetReference, bool alsoDereferenceObjects = true) From e0cb8eeb634be3c9fb9937040f9fcc4c816a9340 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 18 Nov 2024 08:56:38 +0100 Subject: [PATCH 31/99] * adopt file repos for new api --- src/AasxPackageExplorer/MainWindow.xaml.cs | 35 +- src/AasxPackageExplorer/debug.MIHO.script | 1 + .../options-debug.MIHO.json | 883 +++++++++--------- src/AasxPackageLogic/ExplorerMenuFactory.cs | 7 +- .../MainWindowAnyUiDialogs.cs | 38 +- .../PackageContainerAasxFileRepository.cs | 24 +- .../PackageContainerHttpRepoSubset.cs | 60 ++ .../PackageContainerListBase.cs | 28 + .../PackageContainerListFactory.cs | 2 +- .../PackageContainerListHttpRestRepository.cs | 61 +- .../AasxWpfControlLibrary.csproj | 4 + .../PackageContainerListControl.xaml | 205 ++-- .../PackageContainerListControl.xaml.cs | 46 +- .../Resources/AASX_Many.png | Bin 0 -> 2930 bytes .../Resources/AASX_Many_v2.png | Bin 0 -> 1226 bytes 15 files changed, 827 insertions(+), 567 deletions(-) create mode 100644 src/AasxWpfControlLibrary/Resources/AASX_Many.png create mode 100644 src/AasxWpfControlLibrary/Resources/AASX_Many_v2.png diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 0747d7630..334622513 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -21,9 +21,6 @@ This source code may use other Open Source software components (see LICENSE.txt) using Extensions; using Microsoft.Win32; using Newtonsoft.Json; -using NPOI.HPSF; -using NPOI.HSSF.Record; -using Org.BouncyCastle.Asn1.X509; using System; using System.Collections; using System.Collections.Generic; @@ -1002,7 +999,37 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) // what happens on a repo file click RepoListControl.FileDoubleClick += async (senderList, repo, fi) => { - // access + // + // special case: registry / repo + // + + if (repo is PackageContainerListHttpRestRepository restRepo) + { + var fetchContext = new PackageContainerHttpRepoSubsetFetchContext() + { + Record = new ConnectExtendedRecord() + { + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + BaseAddress = restRepo.Endpoint?.ToString() + } + }; + + // refer to (static) function + var res = await DispEditHelperEntities.ExecuteUiForFetchOfElements( + PackageCentral, DisplayContext, + ticket : null, + mainWindow: this, + fetchContext: fetchContext, + preserveEditMode: true, + doEditNewRecord: true, + doCheckTainted: true, + doFetchGoNext: false, + doFetchExec: true); + } + + // + // "normal" file item + // if (repo == null || fi == null) return; diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 1d69c5628..071f5b41e 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -11,6 +11,7 @@ Tool("editkey"); // Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); +Tool("createrepofromapi", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); // Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index df34d9c6a..3e301b5b2 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -1,445 +1,446 @@ { - /* "AasxToLoad" : "sample-techdata.aasx", */ - /* "AasxToLoad": "..\\..\\..\\..\\AasxToolkit\\bin\\Debug\\sample.aasx", */ - /* "AasxToLoad": "sample-hsu.aasx", */ - /* "AasxToLoad": "MTPsample.aasx", */ - /* "AasxToLoad": "sample-generated-modified.aasx", */ - /* "AasxToLoad": "..\\..\\..\\..\\..\\..\\aasx-sample-data\\Develop_AAS\\Sample-Fluiddraw.aasx", */ - /* "AasxRepositoryFn": "..\\..\\..\\..\\..\\Sample_AAS\\aasxrepo-new.json", */ - /* "AasxRepositoryFn": "C:\\Users\\miho\\Desktop\\201112_Festo_AAS_Demo_Koffer\\repo\\FestoDemoBox-RepositoryViaHTTP.json", */ - // "AasxRepositoryFn": "C:\\Users\\miho\\Desktop\\201112_Festo_AAS_Demo_Koffer\\repo3\\Festo-DemoBox-repo-local.json", - /* "AasxRepositoryFn": "C:\\Users\\miho\\Desktop\\201112_Festo_AAS_Demo_Koffer\\Festo_Demo_Box\\Festo-DemoBox-repo-CPX_E.json", */ - /* "AasxRepositoryFn": "C:\\Users\\miho\\Desktop\\new-aasx-repo.json", */ - // "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_demo_case\\Festo-DemoCase-repo-local.json", - // "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-smt-templates.json", - /* "AasxToLoad": "C:\\Users\\miho\\Desktop\\200806_Sample_Repo_with_Fluidic_Plan\\MTPsample.aasx", */ - /* "AasxToLoad": "http://localhost:51310/server/getaasx/0", */ - /* "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\AasxFesto\\AasxFesto\\AasxFctTool\\bin\\Debug\\net472\\test.aasx", */ - /* "AasxToLoad": "C:\\Users\\miho\\Desktop\\Festo_SPAU_VR3_UA.aasx", */ - /* "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\Festo_SPAU_VR3.aasx", */ - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\PxC_aasT_2900542_ELR_H3_I_SC__24DC_500AC_0_6.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\Festo_SPAU-B2R-H-G18FD-L-PNLK-PNVBA-M8U_V3c.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\TempDesktop\\230619 Digi Twin Festo AASX Assessment\\231002_Feedback_1\\8001203_64be10f8-d4c8-4226-a56b-901b9fcbe29c_fb1.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\01_Festo_SPAU_VR3_DPPV2.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\bomtest1.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\leitungssatz_tier1.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02006-2-1_Template_Digital Nameplate_V3_lang.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_Sample_B.aasx", - // "AuxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_Sample_A.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\00_FestoDemoBox-Module-2-Kopie2.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Run2_DNP_plus_CD_SM_step_2.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\Test1.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\SMT_and_SAMM_Showcase_v02.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_TechData_Work\\IDTA 02003-1-2_SubmodelTemplate_TechnicalData_v1.3_workingFile_MIHO.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\g.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\IDTA 02006-3-0_Template_Digital Nameplate_AsciiDoc_Draft_v13_Test_a.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\SMT_and_SAMM_Showcase_v02.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\nearnull.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_TechData_Work\\IDTA 02003-1-2_SubmodelTemplate_TechnicalData_v1.2__with_Draft_1_3_and_AsciiDoc_v01.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Draft_v22.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Examples_v08.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_v20_spiel.aasx", - // "AasxToLoad": "https://cloudrepo.aas-voyager.com/shells/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzY1NDNfNjAzMl8zMDEyXzU5NzM=", - // "AasxToLoad": "https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NGMjA0MTAxUjE0MDAvYWFz", - // "AasxToLoad": "https://eis-data.aas-voyager.com/shell-descriptors/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NGMjA0MTAxUjE0MDAvYWFz", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\210_Copy_Paste\\Sample_AAS.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\310_Plugin_DNP\\Sample_AAS.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\200_Find_Replace\\Sample_SMT.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Examples_v08.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\Syn2tecMachine_P2518_AAS__V3_DL2.aasx", - // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\Syn2tecMachine_P2518_AAS__V3_DL2.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\MTP\\test-data-SMT_MTP.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\00_FestoDemoBox-Module-2-Kopie.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work_spiel.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\a33.aasx", - // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v02.aasx", - // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SAMSON_TROVIS_3730-3_Vorlage_Demo_Achema.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\Prototyp_DPP_SkillsConveyor\\Prototyp_SkillsConveyor.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Aspect_Example_SML_MLP.ttl", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\bugfix\\techdata-prod-class-larger.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\2023_AID1.0_Template_Rework_MIHO.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\aid-test-2.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\aid-test-3_new_opcua.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\OpcUaReadSubmodel\\OpcUaReadSubmodel.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\Aid-HMI-2024_v1.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\robotic_cell_for_demo_suitcase_new-v3.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02006-2-1_Template_Digital Nameplate_V3_Demo_ExportSMT - Kopie.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_play_02.aasx", - "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", - // "AasxRepositoryFn": "C:\\Users\\homi0002\\Desktop\\test3\\new-aasx-repo.json" , - // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\smt_pcn\\SMT_ProductChangeNotification_Draft_play_02.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\dnp30\\IDTA 02006-3-0_Template_Digital Nameplate_AsciiDoc_Draft_v06.aasx", - // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\contactv11\\IDTA 02002-1-1_Template_ContactInformation_AsciiDoc_Draft_v04.aasx", - // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\smt_pcn\\SMT_PCN_Examples_v03.aasx", - // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_PCN_Examples_v06.aasx", - "WindowLeft": 200, - "WindowTop": -1, - "WindowWidth": 900, - "WindowHeight": 600, - "WindowMaximized": false, - "TemplateIdAas": "www.example.com/ids/aas/DDDD_DDDD_DDDD_DDDD", - "TemplateIdAsset": "www.example.com/ids/asset/DDDD_DDDD_DDDD_DDDD", - "TemplateIdSubmodelInstance": "www.example.com/ids/sm/DDDD_DDDD_DDDD_DDDD", - "TemplateIdSubmodelTemplate": "www.example.com/ids/smt/DDDD_DDDD_DDDD_DDDD", - "TemplateIdConceptDescription": "www.example.com/ids/cd/DDDD_DDDD_DDDD_DDDD", - "EclassDir": ".\\eclass\\", - "DefaultLang": "en,de", - "DefaultEmptyLangText": "", /* change for having a preset when creating new language lists */ - "DefaultEmptyReferenceKey": "", /* change for having a preset when creating new References */ - /* "LogoFile": "SpecPI40_t.png", */ - "LogoFile": "PI40_and_IDTA.png", - "QualifiersFile": "qualifier-presets.json", - "ExtensionsPresetFile": "extension-presets.json", - "IdentifierKeyValuePairsFile": "local-identifier-presets.json", - "DataSpecPresetFile": "data-spec-presets.json", - "ContentHome": "https://github.com/admin-shell/io/blob/master/README.md", - "UseFlyovers": true, - "SplashTime": 0, - "PercentageLeftColumn": 12, - "PercentageRightColumn": 60, - "InternalBrowser": false, - "EclassTwoPass": true, - "BackupDir": ".\\backup", - "BackupFiles": 10, - "MaxParallelOps": 1, - "BaseAddresses": [ - "https://cloudrepo.aas-voyager.com/", - "https://eis-data.aas-voyager.com/", - "http://smt-repo.admin-shell-io.com/api/v3.0", - "https://techday2-registry.admin-shell-io.com/", - "http://localhost:5001/", - "http://localhost:5001/api/v3.0/" - ], - "CentralStores": [ - "file://C:\\AasxStore", /* mount point for file shares, WebDAV etc */ - "https://store.example.com/client123" /* example, not implemented */ - ], - "ExtendedConnectionDebug": false, - "AllowFakeResponses": true, - "RestServerHost": "localhost", - "RestServerPort": "1111", - "WriteDefaultOptionsFN": null /* "options-written.json" */, - "IndirectLoadSave": true, - "AccentColors": {}, - "LoadWithoutPrompt": true, - "ShowIdAsIri": false, - "VerboseConnect": true, - "WorkDir": ".\\work", - "CdSortOrder": "Structured", - "ObserveEvents": true, - "CompressEvents": true, - "CheckSmtElements": false, - "DefaultStayConnected": true, - "DefaultUpdatePeriod": 1000, - "StayConnectOptions": "REST-QUEUE", // SIM - /* "DefaultConnectRepositoryLocation": "http://localhost:51310", */ - /* "DefaultConnectRepositoryLocation": "http://admin-shell-io.com:51510", */ - /* "DefaultConnectRepositoryLocation": "https://admin-shell-io.com:51711", */ - "DefaultConnectRepositoryLocation": "https://admin-shell-io.com/51711", - "MqttPublisherOptions": { - "BrokerUrl": "localhost:1883", - "MqttRetain": false, - "EnableFirstPublish": true, - "FirstTopicAAS": "AAS", - "FirstTopicSubmodel": "{aas}/Submodel_{sm}", - "EnableEventPublish": true, - "EventTopic": "Events/aas/{aas}/sm/{sm}/{path}", - "SingleValuePublish": true, - "SingleValueFirstTime": true, - "SingleValueTopic": "Values/aas/{aas}/sm/{sm}/{path}" - }, - "Remarks": [ - "This JSON file might contain special settings for debugging purposes of Michael Hoffmeisters setup." - ], - "PluginPrefer": "ANYUI", - "PluginDll": [ - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginUaNetClient\\bin\\Debug\\net6.0-windows\\AasxPluginUaNetClient.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginProductChangeNotifications\\bin\\Debug\\net6.0-windows\\AasxPluginProductChangeNotifications.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginAssetInterfaceDesc\\bin\\Debug\\net6.0-windows\\AasxPluginAssetInterfaceDesc.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginKnownSubmodels\\bin\\Debug\\net6.0-windows\\AasxPluginKnownSubmodels.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginDigitalNameplate\\bin\\Debug\\net6.0-windows\\AasxPluginDigitalNameplate.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginDocumentShelf\\bin\\Debug\\net6.0-windows\\AasxPluginDocumentShelf.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginContactInformation\\bin\\Debug\\net6.0-windows\\AasxPluginContactInformation.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginGenericForms\\bin\\Debug\\net6.0-windows\\AasxPluginGenericForms.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginAdvancedTextEditor\\bin\\Debug\\net6.0-windows\\AasxPluginAdvancedTextEditor.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginTechnicalData\\bin\\Debug\\net6.0-windows\\AasxPluginTechnicalData.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginBomStructure\\bin\\Debug\\net6.0-windows\\AasxPluginBomStructure.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginPlotting\\bin\\Debug\\net6.0-windows\\AasxPluginPlotting.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginImageMap\\bin\\Debug\\net6.0-windows\\AasxPluginImageMap.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginMtpViewer\\bin\\Debug\\net6.0-windows\\AasxPluginMtpViewer.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginExportTable\\bin\\Debug\\net6.0-windows\\AasxPluginExportTable.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\AasxPluginWebBrowser\\bin\\x64\\Debug\\AasxPluginWebBrowser.dll", - "Args": [] + /* "AasxToLoad" : "sample-techdata.aasx", */ + /* "AasxToLoad": "..\\..\\..\\..\\AasxToolkit\\bin\\Debug\\sample.aasx", */ + /* "AasxToLoad": "sample-hsu.aasx", */ + /* "AasxToLoad": "MTPsample.aasx", */ + /* "AasxToLoad": "sample-generated-modified.aasx", */ + /* "AasxToLoad": "..\\..\\..\\..\\..\\..\\aasx-sample-data\\Develop_AAS\\Sample-Fluiddraw.aasx", */ + /* "AasxRepositoryFn": "..\\..\\..\\..\\..\\Sample_AAS\\aasxrepo-new.json", */ + /* "AasxRepositoryFn": "C:\\Users\\miho\\Desktop\\201112_Festo_AAS_Demo_Koffer\\repo\\FestoDemoBox-RepositoryViaHTTP.json", */ + // "AasxRepositoryFn": "C:\\Users\\miho\\Desktop\\201112_Festo_AAS_Demo_Koffer\\repo3\\Festo-DemoBox-repo-local.json", + /* "AasxRepositoryFn": "C:\\Users\\miho\\Desktop\\201112_Festo_AAS_Demo_Koffer\\Festo_Demo_Box\\Festo-DemoBox-repo-CPX_E.json", */ + /* "AasxRepositoryFn": "C:\\Users\\miho\\Desktop\\new-aasx-repo.json", */ + // "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_demo_case\\Festo-DemoCase-repo-local.json", + // "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-smt-templates.json", + /* "AasxToLoad": "C:\\Users\\miho\\Desktop\\200806_Sample_Repo_with_Fluidic_Plan\\MTPsample.aasx", */ + /* "AasxToLoad": "http://localhost:51310/server/getaasx/0", */ + /* "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\AasxFesto\\AasxFesto\\AasxFctTool\\bin\\Debug\\net472\\test.aasx", */ + /* "AasxToLoad": "C:\\Users\\miho\\Desktop\\Festo_SPAU_VR3_UA.aasx", */ + /* "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\Festo_SPAU_VR3.aasx", */ + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\PxC_aasT_2900542_ELR_H3_I_SC__24DC_500AC_0_6.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\Festo_SPAU-B2R-H-G18FD-L-PNLK-PNVBA-M8U_V3c.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\TempDesktop\\230619 Digi Twin Festo AASX Assessment\\231002_Feedback_1\\8001203_64be10f8-d4c8-4226-a56b-901b9fcbe29c_fb1.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\01_Festo_SPAU_VR3_DPPV2.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\bomtest1.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\leitungssatz_tier1.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02006-2-1_Template_Digital Nameplate_V3_lang.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_Sample_B.aasx", + // "AuxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_Sample_A.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\00_FestoDemoBox-Module-2-Kopie2.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Run2_DNP_plus_CD_SM_step_2.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\Test1.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\SMT_and_SAMM_Showcase_v02.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_TechData_Work\\IDTA 02003-1-2_SubmodelTemplate_TechnicalData_v1.3_workingFile_MIHO.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\g.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\IDTA 02006-3-0_Template_Digital Nameplate_AsciiDoc_Draft_v13_Test_a.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\SMT_and_SAMM_Showcase_v02.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\nearnull.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_TechData_Work\\IDTA 02003-1-2_SubmodelTemplate_TechnicalData_v1.2__with_Draft_1_3_and_AsciiDoc_v01.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Draft_v22.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Examples_v08.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_v20_spiel.aasx", + // "AasxToLoad": "https://cloudrepo.aas-voyager.com/shells/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzY1NDNfNjAzMl8zMDEyXzU5NzM=", + // "AasxToLoad": "https://eis-data.aas-voyager.com/shells/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NGMjA0MTAxUjE0MDAvYWFz", + // "AasxToLoad": "https://eis-data.aas-voyager.com/shell-descriptors/aHR0cHM6Ly9uZXcuYWJiLmNvbS9wcm9kdWN0cy9kZS8yQ1NGMjA0MTAxUjE0MDAvYWFz", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\210_Copy_Paste\\Sample_AAS.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\310_Plugin_DNP\\Sample_AAS.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\aaspe-testing\\200_Find_Replace\\Sample_SMT.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\IDTA_02036-1-0_SMT_ProductChangeNotification_Examples_v08.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\Syn2tecMachine_P2518_AAS__V3_DL2.aasx", + // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\Syn2tecMachine_P2518_AAS__V3_DL2.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\MTP\\test-data-SMT_MTP.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\00_FestoDemoBox-Module-2-Kopie.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work_spiel.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", + "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\a33.aasx", + // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v02.aasx", + // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SAMSON_TROVIS_3730-3_Vorlage_Demo_Achema.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\Prototyp_DPP_SkillsConveyor\\Prototyp_SkillsConveyor.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Aspect_Example_SML_MLP.ttl", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\bugfix\\techdata-prod-class-larger.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\2023_AID1.0_Template_Rework_MIHO.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\aid-test-2.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\aid-test-3_new_opcua.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\OpcUaReadSubmodel\\OpcUaReadSubmodel.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\Aid-HMI-2024_v1.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\robotic_cell_for_demo_suitcase_new-v3.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02006-2-1_Template_Digital Nameplate_V3_Demo_ExportSMT - Kopie.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_play_02.aasx", + "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", + // "AasxRepositoryFn": "C:\\Users\\homi0002\\Desktop\\test3\\new-aasx-repo.json" , + // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\smt_pcn\\SMT_ProductChangeNotification_Draft_play_02.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\dnp30\\IDTA 02006-3-0_Template_Digital Nameplate_AsciiDoc_Draft_v06.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\contactv11\\IDTA 02002-1-1_Template_ContactInformation_AsciiDoc_Draft_v04.aasx", + // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\smt_pcn\\SMT_PCN_Examples_v03.aasx", + // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_PCN_Examples_v06.aasx", + "WindowLeft": 200, + "WindowTop": -1, + "WindowWidth": 900, + "WindowHeight": 600, + "WindowMaximized": false, + "TemplateIdAas": "www.example.com/ids/aas/DDDD_DDDD_DDDD_DDDD", + "TemplateIdAsset": "www.example.com/ids/asset/DDDD_DDDD_DDDD_DDDD", + "TemplateIdSubmodelInstance": "www.example.com/ids/sm/DDDD_DDDD_DDDD_DDDD", + "TemplateIdSubmodelTemplate": "www.example.com/ids/smt/DDDD_DDDD_DDDD_DDDD", + "TemplateIdConceptDescription": "www.example.com/ids/cd/DDDD_DDDD_DDDD_DDDD", + "EclassDir": ".\\eclass\\", + "DefaultLang": "en,de", + "DefaultEmptyLangText": "", /* change for having a preset when creating new language lists */ + "DefaultEmptyReferenceKey": "", /* change for having a preset when creating new References */ + /* "LogoFile": "SpecPI40_t.png", */ + "LogoFile": "PI40_and_IDTA.png", + "QualifiersFile": "qualifier-presets.json", + "ExtensionsPresetFile": "extension-presets.json", + "IdentifierKeyValuePairsFile": "local-identifier-presets.json", + "DataSpecPresetFile": "data-spec-presets.json", + "ContentHome": "https://github.com/admin-shell/io/blob/master/README.md", + "UseFlyovers": true, + "SplashTime": 0, + "PercentageLeftColumn": 12, + "PercentageRightColumn": 60, + "InternalBrowser": false, + "EclassTwoPass": true, + "BackupDir": ".\\backup", + "BackupFiles": 10, + "MaxParallelOps": 1, + "BaseAddresses": [ + "https://cloudrepo.aas-voyager.com/", + "https://eis-data.aas-voyager.com/", + "http://smt-repo.admin-shell-io.com/api/v3.0", + "https://techday2-registry.admin-shell-io.com/", + "http://localhost:5001/", + "http://localhost:5001/api/v3.0/" + ], + "CentralStores": [ + "file://C:\\AasxStore", /* mount point for file shares, WebDAV etc */ + "https://store.example.com/client123" /* example, not implemented */ + ], + "ExtendedConnectionDebug": false, + "AllowFakeResponses": true, + "RestServerHost": "localhost", + "RestServerPort": "1111", + "WriteDefaultOptionsFN": null /* "options-written.json" */, + "IndirectLoadSave": true, + "AccentColors": {}, + "LoadWithoutPrompt": true, + "ShowIdAsIri": false, + "VerboseConnect": true, + "WorkDir": ".\\work", + "CdSortOrder": "Structured", + "ObserveEvents": true, + "CompressEvents": true, + "CheckSmtElements": false, + "DefaultStayConnected": true, + "DefaultUpdatePeriod": 1000, + "StayConnectOptions": "REST-QUEUE", // SIM + /* "DefaultConnectRepositoryLocation": "http://localhost:51310", */ + /* "DefaultConnectRepositoryLocation": "http://admin-shell-io.com:51510", */ + /* "DefaultConnectRepositoryLocation": "https://admin-shell-io.com:51711", */ + "DefaultConnectRepositoryLocation": "https://admin-shell-io.com/51711", + "MqttPublisherOptions": { + "BrokerUrl": "localhost:1883", + "MqttRetain": false, + "EnableFirstPublish": true, + "FirstTopicAAS": "AAS", + "FirstTopicSubmodel": "{aas}/Submodel_{sm}", + "EnableEventPublish": true, + "EventTopic": "Events/aas/{aas}/sm/{sm}/{path}", + "SingleValuePublish": true, + "SingleValueFirstTime": true, + "SingleValueTopic": "Values/aas/{aas}/sm/{sm}/{path}" }, + "Remarks": [ + "This JSON file might contain special settings for debugging purposes of Michael Hoffmeisters setup." + ], + "PluginPrefer": "ANYUI", + "PluginDll": [ + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginUaNetClient\\bin\\Debug\\net6.0-windows\\AasxPluginUaNetClient.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginProductChangeNotifications\\bin\\Debug\\net6.0-windows\\AasxPluginProductChangeNotifications.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginAssetInterfaceDesc\\bin\\Debug\\net6.0-windows\\AasxPluginAssetInterfaceDesc.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginKnownSubmodels\\bin\\Debug\\net6.0-windows\\AasxPluginKnownSubmodels.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginDigitalNameplate\\bin\\Debug\\net6.0-windows\\AasxPluginDigitalNameplate.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginDocumentShelf\\bin\\Debug\\net6.0-windows\\AasxPluginDocumentShelf.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginContactInformation\\bin\\Debug\\net6.0-windows\\AasxPluginContactInformation.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginGenericForms\\bin\\Debug\\net6.0-windows\\AasxPluginGenericForms.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginAdvancedTextEditor\\bin\\Debug\\net6.0-windows\\AasxPluginAdvancedTextEditor.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginTechnicalData\\bin\\Debug\\net6.0-windows\\AasxPluginTechnicalData.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginBomStructure\\bin\\Debug\\net6.0-windows\\AasxPluginBomStructure.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginPlotting\\bin\\Debug\\net6.0-windows\\AasxPluginPlotting.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginImageMap\\bin\\Debug\\net6.0-windows\\AasxPluginImageMap.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginMtpViewer\\bin\\Debug\\net6.0-windows\\AasxPluginMtpViewer.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginExportTable\\bin\\Debug\\net6.0-windows\\AasxPluginExportTable.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\AasxPluginWebBrowser\\bin\\x64\\Debug\\AasxPluginWebBrowser.dll", + "Args": [] + }, - // Festo specific from here on - { - "Path": "..\\..\\..\\..\\..\\..\\..\\..\\AasxFesto\\AasxFesto\\AasxPluginFluiddrawViewer\\bin\\Debug\\net6.0-windows\\AasxPluginFluiddrawViewer.dll", - "Args": [] - }, - { - "Path": "..\\..\\..\\..\\..\\..\\..\\..\\AasxFesto\\AasxFesto\\AasxPluginFluiddrawBom\\bin\\Debug\\net6.0-windows\\AasxPluginFluiddrawBom.dll", - "Args": [] - } - ], - "SecureConnectPresets": [ - { - "Name": "Internet Demo", - "Protocol": { - "Value": "Azure", - "Choices": [ - "Unprotected", - "OpenID Connect", - "Azure" - ] - }, - "AuthorizationServer": { - "Value": "http://auth1.phoenix.com", - "Choices": [ - "http://auth2.phoenix.com", - "http://auth1.phoenix.com", - "http://auth3.phoenix.com" - ] - }, - "AasServer": { - "Value": "http://aas2.phoenix.com", - "Choices": [ - "http://aas3.phoenix.com", - "http://aas2.phoenix.com", - "http://aas1.phoenix.com" - ] - }, - "CertificateFile": { - "Value": "wieso.txt", - "Choices": [ - "C:\\FILE1.TXT", - "C:\\FILE2.TXT", - "C:\\FILE3_dsnchdsbcjbdsjhcbdsjhcbjdsbcjhdsbhjcbdshjcjdshjhcbjhdsbcjhcdsjhbcdsbhjcshjdbcjhdsbjcbdshjcbhsjdbcdsbchjdscbhjdsbhcjhdsb.TXT" - ] - }, - "Password": { - "Value": "sescret", - "Choices": [ - "geheim", - "hidden", - "Pand0ra" - ] - } - }, - { - "Name": "Only Intranet", - "Protocol": { - "Value": "Unprotected", - "Choices": [ - "Unprotected", - "OpenID Connect", - "Azure" - ] - }, - "AuthorizationServer": { - "Value": "http://auth3.phoenix.com", - "Choices": [ - "http://auth2.phoenix.com", - "http://auth1.phoenix.com", - "http://auth3.phoenix.com" - ] - }, - "AasServer": { - "Value": "http://aas1.phoenix.com", - "Choices": [ - "http://aas3.phoenix.com", - "http://aas2.phoenix.com", - "http://aas1.phoenix.com" - ] - }, - "CertificateFile": { - "Value": "wieso22.txt", - "Choices": [ - "C:\\FILE1.TXT", - "C:\\FILE2.TXT", - "C:\\FILE3_dsnchdsbcjbdsjhcbdsjhcbjdsbcjhdsbhjcbdshjcjdshjhcbjhdsbcjhcdsjhbcdsbhjcshjdbcjhdsbjcbdshjcbhsjdbcdsbchjdscbhjdsbhcjhdsb.TXT" - ] - } - } - ], - "IntegratedConnectPresets": [ - { - "Name": "Empty", - "Location": "", - "Username": "", - "Password": "" - }, - { - "Name": "First of admin-shell-io.com", - "Location": "http://admin-shell-io.com:51310/server/getaasx/0", - "Username": "", - "Password": "", - "StayConnected": true - }, - { - "Name": "DEMO", - "Location": "http://demo/", - "Username": "Foo", - "Password": "Bar", - "AutoClose": false - }, - { - "Name": "Long loading of admin-shell-io.com", - "Location": "http://admin-shell-io.com:51410/server/getaasx/3", - "Username": "", - "Password": "", - "StayConnected": true, - "AutoClose": false - }, - { - "Name": "Latest testing", - "Location": "http://admin-shell-io.com:52001/server/getaasx/0", - "Username": "", - "Password": "", - "StayConnected": true, - "AutoClose": false - } - ], - "FontSizePreset": 16, - "ScriptPresets": [ - { - "Name": "Test1", - "Lines": [ - "Select(\"AAS\", \"First\");", - "Sleep(1000);", - "Location(\"Push\");", - "Sleep(1000);", - "Select(\"Submodel\", \"First\");", - "Sleep(1000);", - "while (true) {", - " rf = Select(\"This\");", - " WriteLine(\"Approaching Referable \" + rf);", - " if (Select(\"Submodel\", \"Next\") == null) break;", - "}" - ] - }, - { - "Name": "Test2", - "Lines": [ - "Select(\"Submodel\", \"First\");", - "Sleep(1000);", - "Tool(\"ImportTimeSeries\", ", - " \"Format\", \"Excel\", \"File\", \"Test.xlsx\", ", - " \"ColData\", \"3\" );", - "Sleep(1000);", - "WriteLine(\"Done\");" - ] - }, - { - "Name": "Select Test 3", - "Lines": [ - "Select(\"AAS\", \"Next\");" - ] - }, - { - "Name": "Select Test 4", - "Lines": [ - "Select(\"AAS\", \"Prev\");" - ] - }, - { - "Name": "Test 5", - "Lines": [ - "Tool(\"EditMenu\", \"Mode\", true);", - "res = Tool(\"ToolsFindText\", \"FindText\", \"DE\"); ", - "num = 0;", - "while (res) {", - " x = Select(\"This\"); ", - " WriteLine(num, \":\", x.idShort);", - " num = num + 1;", - " if (num > 18)", - " break;", - " res = Tool(\"ToolsFindForward\");", - " Sleep(200);", - "}" - ] - }, - { - "Name": "Test 6", - "Lines": [ - "Tool(\"EditMenu\", \"Mode\", true);", - "res = Tool(\"ToolsReplaceText\", \"FindText\", \"DE\", \"ReplaceText\", \"GDR\", \"Do\", \"All\"); " - ] - }, - { - "Name": "AsciiDoc - Just export (Ctrl+Shift+7)", - "Lines": [ - "Tool(\"LocationPush\");", - "Select(\"Submodel\", \"First\");", - "Select(\"Submodel\", \"Next\");", - "Tool(\"ExportSmtAsciiDoc\", \"File\", \"C:\\Users\\homi0002\\Desktop\\tmp\\new.zip\");", - "Tool(\"LocationPop\");" - ] - }, - { - "Name": "AsciiDoc - Export and view (Ctrl+Shift+8)", - "Lines": [ - "Tool(\"LocationPush\");", - "Select(\"Submodel\", \"First\");", - "Select(\"Submodel\", \"Next\");", - "Tool(\"ExportSmtAsciiDoc\", \"File\", \"C:\\Users\\homi0002\\Desktop\\tmp\\new.zip\", \"ExportHtml\", \"true\", \"ExportPdf\", \"false\", \"AntoraStyle\", \"false\", \"ViewResult\", \"true\");", - "Tool(\"LocationPop\");" - ] - } - ], - "ScriptLoglevel": 2, - "ScriptLaunchWithoutPrompt": true, - "ScriptExecuteSystem": true - // "ScriptCmd": "WriteLine(\"Hallo\");" - // "ScriptFn" : "test-script.txt" + // Festo specific from here on + { + "Path": "..\\..\\..\\..\\..\\..\\..\\..\\AasxFesto\\AasxFesto\\AasxPluginFluiddrawViewer\\bin\\Debug\\net6.0-windows\\AasxPluginFluiddrawViewer.dll", + "Args": [] + }, + { + "Path": "..\\..\\..\\..\\..\\..\\..\\..\\AasxFesto\\AasxFesto\\AasxPluginFluiddrawBom\\bin\\Debug\\net6.0-windows\\AasxPluginFluiddrawBom.dll", + "Args": [] + } + ], + "SecureConnectPresets": [ + { + "Name": "Internet Demo", + "Protocol": { + "Value": "Azure", + "Choices": [ + "Unprotected", + "OpenID Connect", + "Azure" + ] + }, + "AuthorizationServer": { + "Value": "http://auth1.phoenix.com", + "Choices": [ + "http://auth2.phoenix.com", + "http://auth1.phoenix.com", + "http://auth3.phoenix.com" + ] + }, + "AasServer": { + "Value": "http://aas2.phoenix.com", + "Choices": [ + "http://aas3.phoenix.com", + "http://aas2.phoenix.com", + "http://aas1.phoenix.com" + ] + }, + "CertificateFile": { + "Value": "wieso.txt", + "Choices": [ + "C:\\FILE1.TXT", + "C:\\FILE2.TXT", + "C:\\FILE3_dsnchdsbcjbdsjhcbdsjhcbjdsbcjhdsbhjcbdshjcjdshjhcbjhdsbcjhcdsjhbcdsbhjcshjdbcjhdsbjcbdshjcbhsjdbcdsbchjdscbhjdsbhcjhdsb.TXT" + ] + }, + "Password": { + "Value": "sescret", + "Choices": [ + "geheim", + "hidden", + "Pand0ra" + ] + } + }, + { + "Name": "Only Intranet", + "Protocol": { + "Value": "Unprotected", + "Choices": [ + "Unprotected", + "OpenID Connect", + "Azure" + ] + }, + "AuthorizationServer": { + "Value": "http://auth3.phoenix.com", + "Choices": [ + "http://auth2.phoenix.com", + "http://auth1.phoenix.com", + "http://auth3.phoenix.com" + ] + }, + "AasServer": { + "Value": "http://aas1.phoenix.com", + "Choices": [ + "http://aas3.phoenix.com", + "http://aas2.phoenix.com", + "http://aas1.phoenix.com" + ] + }, + "CertificateFile": { + "Value": "wieso22.txt", + "Choices": [ + "C:\\FILE1.TXT", + "C:\\FILE2.TXT", + "C:\\FILE3_dsnchdsbcjbdsjhcbdsjhcbjdsbcjhdsbhjcbdshjcjdshjhcbjhdsbcjhcdsjhbcdsbhjcshjdbcjhdsbjcbdshjcbhsjdbcdsbchjdscbhjdsbhcjhdsb.TXT" + ] + } + } + ], + "IntegratedConnectPresets": [ + { + "Name": "Empty", + "Location": "", + "Username": "", + "Password": "" + }, + { + "Name": "First of admin-shell-io.com", + "Location": "http://admin-shell-io.com:51310/server/getaasx/0", + "Username": "", + "Password": "", + "StayConnected": true + }, + { + "Name": "DEMO", + "Location": "http://demo/", + "Username": "Foo", + "Password": "Bar", + "AutoClose": false + }, + { + "Name": "Long loading of admin-shell-io.com", + "Location": "http://admin-shell-io.com:51410/server/getaasx/3", + "Username": "", + "Password": "", + "StayConnected": true, + "AutoClose": false + }, + { + "Name": "Latest testing", + "Location": "http://admin-shell-io.com:52001/server/getaasx/0", + "Username": "", + "Password": "", + "StayConnected": true, + "AutoClose": false + } + ], + "FontSizePreset": 16, + "ScriptPresets": [ + { + "Name": "Test1", + "Lines": [ + "Select(\"AAS\", \"First\");", + "Sleep(1000);", + "Location(\"Push\");", + "Sleep(1000);", + "Select(\"Submodel\", \"First\");", + "Sleep(1000);", + "while (true) {", + " rf = Select(\"This\");", + " WriteLine(\"Approaching Referable \" + rf);", + " if (Select(\"Submodel\", \"Next\") == null) break;", + "}" + ] + }, + { + "Name": "Test2", + "Lines": [ + "Select(\"Submodel\", \"First\");", + "Sleep(1000);", + "Tool(\"ImportTimeSeries\", ", + " \"Format\", \"Excel\", \"File\", \"Test.xlsx\", ", + " \"ColData\", \"3\" );", + "Sleep(1000);", + "WriteLine(\"Done\");" + ] + }, + { + "Name": "Select Test 3", + "Lines": [ + "Select(\"AAS\", \"Next\");" + ] + }, + { + "Name": "Select Test 4", + "Lines": [ + "Select(\"AAS\", \"Prev\");" + ] + }, + { + "Name": "Test 5", + "Lines": [ + "Tool(\"EditMenu\", \"Mode\", true);", + "res = Tool(\"ToolsFindText\", \"FindText\", \"DE\"); ", + "num = 0;", + "while (res) {", + " x = Select(\"This\"); ", + " WriteLine(num, \":\", x.idShort);", + " num = num + 1;", + " if (num > 18)", + " break;", + " res = Tool(\"ToolsFindForward\");", + " Sleep(200);", + "}" + ] + }, + { + "Name": "Test 6", + "Lines": [ + "Tool(\"EditMenu\", \"Mode\", true);", + "res = Tool(\"ToolsReplaceText\", \"FindText\", \"DE\", \"ReplaceText\", \"GDR\", \"Do\", \"All\"); " + ] + }, + { + "Name": "AsciiDoc - Just export (Ctrl+Shift+7)", + "Lines": [ + "Tool(\"LocationPush\");", + "Select(\"Submodel\", \"First\");", + "Select(\"Submodel\", \"Next\");", + "Tool(\"ExportSmtAsciiDoc\", \"File\", \"C:\\Users\\homi0002\\Desktop\\tmp\\new.zip\");", + "Tool(\"LocationPop\");" + ] + }, + { + "Name": "AsciiDoc - Export and view (Ctrl+Shift+8)", + "Lines": [ + "Tool(\"LocationPush\");", + "Select(\"Submodel\", \"First\");", + "Select(\"Submodel\", \"Next\");", + "Tool(\"ExportSmtAsciiDoc\", \"File\", \"C:\\Users\\homi0002\\Desktop\\tmp\\new.zip\", \"ExportHtml\", \"true\", \"ExportPdf\", \"false\", \"AntoraStyle\", \"false\", \"ViewResult\", \"true\");", + "Tool(\"LocationPop\");" + ] + } + ], + "ScriptLoglevel": 2, + "ScriptLaunchWithoutPrompt": true, + "ScriptExecuteSystem": true + // "ScriptCmd": "WriteLine(\"Hallo\");" + // "ScriptFn" : "test-script.txt" } diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index 22ceed980..abd9a2447 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -49,8 +49,8 @@ public static AasxMenu CreateMainMenu() childs: (new AasxMenu()) .AddWpfBlazor(name: "New", header: "_New …", inputGesture: "Ctrl+N", help: "Create new AASX package.") - .AddWpfBlazor(name: "Open", header: "_Open …", inputGesture: "Ctrl+O", - help: "Open existing AASX package.", + .AddWpfBlazor(name: "Open", header: "_Open (local) …", inputGesture: "Ctrl+O", + help: "Open (local) existing AASX package.", args: new AasxMenuListOfArgDefs() .Add("File", "Source filename including a path and extension.")) .AddWpfBlazor(name: "FileRepoQuery", header: "Query open repositories/ registries …", inputGesture: "F12", @@ -123,6 +123,9 @@ public static AasxMenu CreateMainMenu() args: new AasxMenuListOfArgDefs() .AddFromReflection(new PackageContainerHttpRepoSubset.ConnectExtendedRecord())) .AddWpfBlazor(name: "ApiUploadAssistant", header: "Upload assistant …") + .AddWpf(name: "CreateRepoFromApi", header: "Create (local) file repository from API base …", + args: new AasxMenuListOfArgDefs() + .AddFromReflection(new PackageContainerHttpRepoSubset.ConnectExtendedRecord())) ) .AddSeparator() .AddMenu(header: "AASX File Repository …", childs: (new AasxMenu()) diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 0476a237a..ea63ec81f 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -609,6 +609,41 @@ await PackageContainerHttpRepoSubset.PerformUploadAssistant( } } + if (cmd == "createrepofromapi") + { + ticket.StartExec(); + + // edit infos + // TODO: adopt from? + var record = (null) ?? new PackageContainerHttpRepoSubset.ConnectExtendedRecord(); + + var uiRes = await PackageContainerHttpRepoSubset.PerformConnectExtendedDialogue( + ticket, DisplayContext, + "Specify Registry or Repository base", + record, + scope: ConnectExtendedScope.BaseInfo); + + if (!uiRes) + return; + + if (record?.BaseAddress.HasContent() != true) + { + LogErrorToTicket(ticket, "No base address for repository given!"); + return; + } + + // ok + if (record.BaseType == ConnectExtendedRecord.BaseTypeEnum.Repository) + { + var fr = new PackageContainerListHttpRestRepository( + record.BaseAddress, this.PackageCentral.CentralRuntimeOptions); + fr.Header = "New remote Repository"; + MainWindow.UiShowRepositories(visible: true); + PackageCentral.Repositories.AddAtTop(fr); + } + } + + if (cmd == "comparesmt") { // start @@ -779,7 +814,8 @@ await val.PerformDialogue(ticket, DisplayContext, // ok if (endpoint.Contains("old")) { - var fr = new PackageContainerListHttpRestRepository(endpoint); + var fr = new PackageContainerListHttpRestRepository(endpoint, + PackageCentral.CentralRuntimeOptions); await fr.SyncronizeFromServerAsync(); MainWindow.UiShowRepositories(visible: true); PackageCentral.Repositories.AddAtTop(fr); diff --git a/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/PackageContainerAasxFileRepository.cs b/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/PackageContainerAasxFileRepository.cs index 383b526d4..c895efbd5 100644 --- a/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/PackageContainerAasxFileRepository.cs +++ b/src/AasxPackageLogic/PackageCentral/AasxFileServerInterface/PackageContainerAasxFileRepository.cs @@ -20,6 +20,7 @@ public class PackageContainerAasxFileRepository : PackageContainerListBase private AasxFileServerInterfaceService _aasxFileService; public readonly PackCntRuntimeOptions CentralRuntimeOptions; + /// /// OpenIdClient to be used by the repository/ registry. To be set, when /// first time used. @@ -27,25 +28,18 @@ public class PackageContainerAasxFileRepository : PackageContainerListBase public OpenIdClientInstance OpenIdClient = null; - public PackageContainerAasxFileRepository(string inputText, PackCntRuntimeOptions centralRuntimeOptions) + public PackageContainerAasxFileRepository(string endpoint, PackCntRuntimeOptions centralRuntimeOptions) { - // dead-csharp off - //if (inputText.Contains('?')) - //{ - // var splitTokens = inputText.Split(new[] { '?' }, 2); - // if (splitTokens[1].Equals("asp.net", StringComparison.OrdinalIgnoreCase)) - // { - // IsAspNetConnection = true; - // } - // inputText = splitTokens[0]; - //} - // dead-csharp on - this.Header = "AASX File Server Repository"; + this.Header = "AASX File Repository"; IsAspNetConnection = true; + // always have a location - Endpoint = new Uri(inputText); + Endpoint = new Uri(endpoint); - _aasxFileService = new AasxFileServerInterfaceService(inputText); + // Note: to be checked + _aasxFileService = new AasxFileServerInterfaceService(endpoint); + + // remember CentralRuntimeOptions = centralRuntimeOptions; } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 542480132..de8cbe43d 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -315,6 +315,16 @@ public string ToQueryString() } } + // + // GENERAL + // + + public static Uri BuildUriForDescription(Uri baseUri) + { + // try combine + return CombineUri(baseUri, $"description"); + } + // // REPO // @@ -548,6 +558,56 @@ public static Uri BuildUriForRegistryAasByAssetLink(Uri baseUri, string id, bool return CombineUri(baseUri, $"lookup/shells?assetId={assenc}"); } + // + // translate PROFILES + // + + public class ProfileDescription + { + public string Id; + public string Name; + public string Abbreviation; + + public ProfileDescription() { } + + public ProfileDescription(string id, string name, string abbreviation) + { + Id = id; + Name = name; + Abbreviation = abbreviation; + } + } + + public static ProfileDescription[] ProfileDescriptions = + { + new ProfileDescription("https://admin-shell.io/aas/API/3/0/AssetAdministrationShellServiceSpecification/SSP-001", "AAS Full Profile ", "All/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/AssetAdministrationShellServiceSpecification/SSP-002", "AAS Read Profile", "All/Read"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-001", "Submodel Full Profile", "SM/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-002", "Submodel Read Profile", "SM/Read"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003", "Submodel Value Profile", "SM/Value"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/AasxFileServerServiceSpecification/SSP-001", "AASX File Server Full Profile", "AASX/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-001 ", "Asset Administration Shell Registry Full Profile", "AAS-REG/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-002", "AAS Registry Read Profile", "AAS-REG/Read"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-001 ", "Submodel Registry Full Profile", "SM-REG/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-002 ", "Submodel Registry Read Profile", "SM-REG/Read"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/DiscoveryServiceSpecification/SSP-001", "Discovery Service Full Profile", "DISC/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRepositoryServiceSpecification/SSP-001", "AAS Repository Full Profile", "AAS-REPO/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRepositoryServiceSpecification/SSP-002", "AAS Repository Read Profile", "AAS-REPO/Read"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-001", "Submodel Repository Full Profile", "SM-REPO/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-001", "Submodel Repository Read Profile", "SM-REPO/Read"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-003", "Submodel Repository Template Profile", "SMT-REPO/Full"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/SubmodelServiceSpecification/SSP-004", "Submodel Repository Template Read Profile", "SMT-REPO/Read"), + new ProfileDescription("https://admin-shell.io/aas/API/3/0/ConceptDescriptionRepositoryServiceSpecification/SSP-001", "Concept Description Repository Full Profile", "CD-REPO/Full") + }; + + public static ProfileDescription FindProfileDescription(string input) + { + ProfileDescription pdFound = null; + foreach (var pd in ProfileDescriptions) + if (pd.Id.Equals(input)) + return pd; + return null; + } // // ALL diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs index f4525dd1a..fef6274f8 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs @@ -16,6 +16,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Collections.ObjectModel; using System.IO; using System.Linq; +using System.Threading.Tasks; using Aas = AasCore.Aas3_0; namespace AasxPackageLogic.PackageCentral @@ -31,6 +32,8 @@ namespace AasxPackageLogic.PackageCentral /// AASX Package file is to be loaded and hosted and functinality, HOW this can be done. /// This class is intended to be a base class, so classes for local repos, AAS repos, AAS registries are /// deriving from it. + /// In Nov 2024, the idea is incrementally adjusted to accomodate Registries and Repositories. + /// The process here is not perfect .. /// public class PackageContainerListBase : IPackageContainerFind { @@ -57,6 +60,31 @@ public class PackageContainerListBase : IPackageContainerFind [JsonIgnore] public double DefaultAnimationTime = 2.0d; + /// + /// True if it represents a finalized list of items. + /// False e.g. for a Repository or Regeistry, where the count of items varies. + /// + public bool IsStaticList = true; + + /// + /// Acquire some status data, may take a while + /// + /// + virtual public async Task PrepareStatus() + { + await Task.Yield(); + return false; + } + + /// + /// Render a multi line status; maybe with async acquired stats. + /// + /// + virtual public string GetMultiLineStatusInfo() + { + return ""; + } + // // Basic memeber management // diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListFactory.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListFactory.cs index 0d2147ee1..103341deb 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListFactory.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListFactory.cs @@ -38,7 +38,7 @@ public static PackageContainerListBase GuessAndCreateNew(string location) var ll = location.Trim().ToLower(); if (ll.StartsWith("http://") || ll.StartsWith("https")) { - var repo = new PackageContainerListHttpRestRepository(location); + var repo = new PackageContainerListHttpRestRepository(location, null); return repo; } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs index e2124da88..6db718efa 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs @@ -34,6 +34,10 @@ public class PackageContainerListHttpRestRepository : PackageContainerListHttpRe private PackageConnectorHttpRest _connector; + public string ServerStatus { get; private set; } = "Status unknown!"; + + public readonly PackCntRuntimeOptions CentralRuntimeOptions; + /// /// REST endpoint of the AAS repository, that is, without /shells etc. but /// with e.g. /api/v3.0/ @@ -45,8 +49,15 @@ public class PackageContainerListHttpRestRepository : PackageContainerListHttpRe // Constructor // - public PackageContainerListHttpRestRepository(string location) + public PackageContainerListHttpRestRepository(string location, PackCntRuntimeOptions centralRuntimeOptions) { + // infos + this.Header = "AAS API Repository"; + this.IsStaticList = false; + + // remember + CentralRuntimeOptions = centralRuntimeOptions; + // always have a location Endpoint = new Uri(location); @@ -91,6 +102,54 @@ public async Task SyncronizeFromServerAsync() return true; } + override public async Task PrepareStatus() + { + // for exception handling, mark as potential flaw! + ServerStatus = "Error retrieving /description !"; + + try + { + var resObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( + PackageContainerHttpRepoSubset.BuildUriForDescription(Endpoint), + runtimeOptions: null); + + if (resObj == null || !PackageContainerHttpRepoSubset.HasProperty(resObj, "profiles")) + return false; + + // carefully access + var abbrevs = new List(); + foreach (var pr in resObj.profiles) + { + // carefully access + string profile = ("" + pr).Trim(); + if (profile?.HasContent() != true) + continue; + + // find abbrevs + var pd = PackageContainerHttpRepoSubset.FindProfileDescription(profile); + if (pd != null) + abbrevs.Add(pd.Abbreviation); + } + + if (abbrevs.Count > 0) + ServerStatus = "Profiles: " + string.Join(", ", abbrevs); + else + ServerStatus = "No profiles described!"; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + + return false; + } + + override public string GetMultiLineStatusInfo() + { + return "REPOSITORY at base " + Endpoint + "\n" + + "" + ServerStatus; + } + /// /// Retrieve the full location specification of the item w.r.t. to persistency container /// (filesystem, HTTP, ..) diff --git a/src/AasxWpfControlLibrary/AasxWpfControlLibrary.csproj b/src/AasxWpfControlLibrary/AasxWpfControlLibrary.csproj index 8068d998d..8d209afa8 100644 --- a/src/AasxWpfControlLibrary/AasxWpfControlLibrary.csproj +++ b/src/AasxWpfControlLibrary/AasxWpfControlLibrary.csproj @@ -12,6 +12,8 @@ + + @@ -35,6 +37,8 @@ + + diff --git a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml index e191faeb6..5c8f65e71 100644 --- a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml +++ b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml @@ -189,105 +189,112 @@ Foreground="#707070" VerticalContentAlignment="Center" FontWeight="Bold" TextChanged="TextBox_TextChanged"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml.cs b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml.cs index 66f4b28f5..9f029586f 100644 --- a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml.cs +++ b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml.cs @@ -61,6 +61,8 @@ public PackageContainerListControl() InitializeComponent(); } + private int _ticksTillReLoadStatus = 0; + private void UserControl_Loaded(object sender, RoutedEventArgs e) { // might attach to data context @@ -73,10 +75,11 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e) // redraw RedrawStatus(); + _ticksTillReLoadStatus = 20; // Timer for animations System.Windows.Threading.DispatcherTimer MainTimer = new System.Windows.Threading.DispatcherTimer(); - MainTimer.Tick += MainTimer_Tick; + MainTimer.Tick += async (s,e) => await MainTimer_Tick(s,e); MainTimer.Interval = new TimeSpan(0, 0, 0, 0, 100); MainTimer.Start(); } @@ -111,12 +114,38 @@ public void RedrawStatus() if (!header.HasContent()) header = "Unnamed repository"; TextBoxRepoHeader.Text = "" + header; + + // set one or many + if (theFileRepository?.IsStaticList == true) + { + // "normal" list behavior + TabControlOneOrMany.SelectedItem = TabItemRepoList; + } + else + { + // only a static display + TabControlOneOrMany.SelectedItem = TabItemManyRepo; + TextBlockManyInfo.Text = "" + theFileRepository?.GetMultiLineStatusInfo(); + } } - private void MainTimer_Tick(object sender, EventArgs e) + private async Task MainTimer_Tick(object sender, EventArgs e) { + await Task.Yield(); + if (this.theFileRepository != null) this.theFileRepository.DecreaseVisualTimeBy(0.1); + + // async reload status, once? + if (_ticksTillReLoadStatus > 0) + { + _ticksTillReLoadStatus--; + if (_ticksTillReLoadStatus < 1) + { + await theFileRepository?.PrepareStatus(); + RedrawStatus(); + } + } } private void UserControl_MouseLeave(object sender, MouseEventArgs e) @@ -127,11 +156,22 @@ private void UserControl_MouseLeave(object sender, MouseEventArgs e) private void RepoList_MouseDoubleClick(object sender, MouseButtonEventArgs e) { if (sender == this.RepoList && e.LeftButton == MouseButtonState.Pressed) + { // hoping, that correct item is selected - this.FileDoubleClick?.Invoke(this, theFileRepository, + this.FileDoubleClick?.Invoke(this, theFileRepository, this.RepoList.SelectedItem as PackageContainerRepoItem); + } } + private void ImageManyIcons_MouseDown(object sender, MouseButtonEventArgs e) + { + if (sender == ImageManyIcons && e.ClickCount == 2) + { + this.FileDoubleClick?.Invoke(this, theFileRepository, null); + } + } + + private async void Button_Click(object sender, RoutedEventArgs e) { if (sender == this.ButtonQuery) diff --git a/src/AasxWpfControlLibrary/Resources/AASX_Many.png b/src/AasxWpfControlLibrary/Resources/AASX_Many.png new file mode 100644 index 0000000000000000000000000000000000000000..119419127390fcbc8940b4849325be5a40556fad GIT binary patch literal 2930 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 AoB#j- literal 0 HcmV?d00001 diff --git a/src/AasxWpfControlLibrary/Resources/AASX_Many_v2.png b/src/AasxWpfControlLibrary/Resources/AASX_Many_v2.png new file mode 100644 index 0000000000000000000000000000000000000000..de0736b7045c61cf9cc3fbe9f5192f95cd950642 GIT binary patch literal 1226 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw3=&b&bO2I}#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@gBC_=LFr|Np;p_VfAc-u(FScf!J#FW>yybNthm10R-cdHemx--pkB96tNG zf8om`XFq@V^ykveZ)>VG%kdQt_MiMTeZ{N%w#WCM z{CxlEkA3XT`pM5OT>lo3eb*uGmO;R^g0{yGp8iaze4yZb`Rlj8>vp~S`u*>^-S4&@ z_;Bm~4`GHKH-KK?EbxddW?3CK2NWMbe0 z(osO1+0FtM&j7MPAOVO$`e8JRl?=e>n841!0+cr}GB#jb05KD!o^=7loJl}72rvOn zVFIfRva|rQpt=kV3_!BUOVo}8`TacuRJY61#WBRg`{}g&@>vEV?e&gPnnBm1cAtx0 z62X$2z3j@myZhh&-&rOkq`;_Vdwcd=TYVd~o&I|oRl*O5C+%W0)=2cI!^UwV0zj|lyotjmfCvn{5KmSMC zc-`#TR@$8t+Kp#d=Kf6KYv^NaS$Kca`jp#!xq(vK-h_Ty`_8;mY^lz@If};{7C)5} z@9>&=H`1v4anWS~)nylt=%${Z_O(p4t3mamZNrqXhRK2ZN;38?40*=7p*rcKS?UX4$FAW5x;#L@iHfM(b{zcsVnYz5Jy2Da*UvYcg5Y&vqy) zO#CYFb7GL+YxPqVZqbY8{4hK1BIf+W^nTO&cRQOK^5@LClzPD4{^M1Jo%8qWNguA) zdH=2TXkB<8--P+6g-Zl%ll+A)K5{*+d&cy%utJN9r;w@O&i7Ne73%|e3lsWI=`DD4 z^=3%$+s50^X1#U|58oZ}GVbrRh@}p3OfJ7&YRXQ%$_k&ya>F$+#MbWaO~vljcPA~r zyGSE0cJca{&4KwL2REi%w`F+eXuofg(Y{C!|4Hp6hCs#XQH^1H16hwx5??R);Zv5* z%mz2nHL3Pno_wmDZOdY^DnZx#%?W|lyH6v(E_1It_E7TIT5X2=tIStD*z)x5+Ix9Y zs#{WiH=eBB7yR3s`OMm?#aw>g%NA{j*?IU(mGZV2ncMGo3JYdmoBb<Ht~Afz(xYg+!Mp5z zjPjy?_q Date: Mon, 18 Nov 2024 22:39:38 +0100 Subject: [PATCH 32/99] * update --- src/AasxPackageExplorer/MainWindow.xaml.cs | 121 ++++++++++-------- src/AasxPackageExplorer/debug.MIHO.script | 4 +- src/AasxPackageLogic/IMainWindow.cs | 4 +- src/AasxPackageLogic/MainWindowHeadless.cs | 36 +++++- .../PackageCentral/IPackageContainerFind.cs | 5 +- .../PackageContainerHttpRepoSubset.cs | 69 +++++++++- .../PackageContainerListBase.cs | 16 ++- .../PackageContainerListHttpRestRepository.cs | 35 +++++ .../PackageContainerListOfList.cs | 10 +- .../PackageContainerListControl.xaml | 4 +- .../PackageContainerListControl.xaml.cs | 1 - .../PackageContainerListOfListControl.xaml.cs | 10 +- .../Data/BlazorSession.MainWindow.cs | 8 +- 13 files changed, 248 insertions(+), 75 deletions(-) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 334622513..aec9e6f2b 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -1758,24 +1758,20 @@ private void UiHandleReRenderAnyUiInEntityPanel( public async Task UiSearchRepoAndExtendEnvironmentAsync( AdminShellPackageEnvBase packEnv, - Aas.IReference workRef) + Aas.IReference workRef = null, + string fullItemLocation = null, + bool tryDisplay = false) { await Task.Yield(); // access - if (packEnv == null || workRef?.IsValid() != true) + if (packEnv == null || (workRef?.IsValid() != true && fullItemLocation?.HasContent() != true)) return null; // check if env is dynamic fetch if (packEnv is not AdminShellPackageDynamicFetchEnv dynPack) return null; - //// try to find the last repo/ registry data - //DisplayElements.FindAllVisualElementTopToIdentifiable() - // .Where((ve) => ve is VisualElementEnvironmentItem veei - // && veei.theItemType == VisualElementEnvironmentItem.ItemType.Package - // && veei.the) - // try get a copy of the fetch record var context = (dynPack.GetContext() as PackageContainerHttpRepoSubsetFetchContext); var record = (context?.Record as ConnectExtendedRecord)?.Copy(); @@ -1786,58 +1782,77 @@ private void UiHandleReRenderAnyUiInEntityPanel( if (record.BaseAddress?.HasContent() != true) return null; - // what to search? - string fullItemLocation = null; - // search for AAS? - if (workRef.Count() >= 1 && workRef.Keys[0].Type == KeyTypes.AssetAdministrationShell) + if (workRef?.IsValid() == true) { - // want to search for an AAS - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleAas); - record.AasId = workRef.Keys[0].Value; - fullItemLocation = PackageContainerHttpRepoSubset.BuildLocationFrom(record); - } + if (workRef.Count() >= 1 && workRef.Keys[0].Type == KeyTypes.AssetAdministrationShell) + { + // want to search for an AAS + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleAas); + record.AasId = workRef.Keys[0].Value; + fullItemLocation = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + } - // search for Asset? - if (workRef.Count() >= 1 && workRef.Keys[0].Type == KeyTypes.GlobalReference) - { - // want to search for an Asset? - record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AasByAssetLink); - record.AssetId = workRef.Keys[0].Value; - fullItemLocation = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + // search for Asset? + if (workRef.Count() >= 1 && workRef.Keys[0].Type == KeyTypes.GlobalReference) + { + // want to search for an Asset? + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AasByAssetLink); + record.AssetId = workRef.Keys[0].Value; + fullItemLocation = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + } } - // search any? - if (fullItemLocation != null) { - // try to load - // TODO: take over those options from existing container - var containerOptions = new PackageContainerHttpRepoSubset. - PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), - record); - - var newIdfs = new List(); - var loadedIdfs = new List(); - var loadRes = await PackageContainerHttpRepoSubset.LoadFromSourceInternalAsync( - fullItemLocation: fullItemLocation, - targetEnv: packEnv, - loadNew: false, - trackNewIdentifiables: newIdfs, - trackLoadedIdentifiables: loadedIdfs, - containerOptions: containerOptions, - runtimeOptions: PackageCentral.CentralRuntimeOptions); + // any info + if (fullItemLocation?.HasContent() != true) + return null; - if (loadRes == null || newIdfs.Count < 1) - return null; + // try to load + // TODO: take over those options from existing container + var containerOptions = new PackageContainerHttpRepoSubset. + PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + record); + + var newIdfs = new List(); + var loadedIdfs = new List(); + var loadRes = await PackageContainerHttpRepoSubset.LoadFromSourceInternalAsync( + fullItemLocation: fullItemLocation, + targetEnv: packEnv, + loadNew: false, + trackNewIdentifiables: newIdfs, + trackLoadedIdentifiables: loadedIdfs, + containerOptions: containerOptions, + runtimeOptions: PackageCentral.CentralRuntimeOptions); + + if (loadRes == null || newIdfs.Count < 1) + return null; + + // rebuild display elements + DisplayElements.RebuildAasxElements( + PackageCentral, PackageCentral.Selector.Main, MainMenu?.IsChecked("EditMenu") == true, + lazyLoadingFirst: true); - // rebuild display elements - DisplayElements.RebuildAasxElements( - PackageCentral, PackageCentral.Selector.Main, MainMenu?.IsChecked("EditMenu") == true, - lazyLoadingFirst: true); + var newIdf = newIdfs.FirstOrDefault(); - return newIdfs.FirstOrDefault(); + // display + if (tryDisplay) + { + var veFound = this.DisplayElements.SearchVisualElementOnMainDataObject(newIdf, alsoDereferenceObjects: true); + if (veFound != null) + { + // show ve + DisplayElements.TrySelectVisualElement(veFound, wishExpanded: true); + // remember in history + Logic?.LocationHistory?.Push(veFound); + // fake selection + RedrawElementView(); + DisplayElements.Refresh(); + TakeOverContentEnable(false); + } } - return null; + // the end + return newIdf; } private async Task UiHandleNavigateTo( @@ -1890,9 +1905,9 @@ private async Task UiHandleNavigateTo( PackageContainerRepoItem fi = null; if (work.Keys[0].Type == Aas.KeyTypes.GlobalReference) //TODO (jtikekar, 0000-00-00): KeyTypes.AssetInformation - fi = PackageCentral.Repositories.FindByAssetId(work.Keys[0].Value.Trim()); + fi = await PackageCentral.Repositories.FindByAssetId(work.Keys[0].Value.Trim()); if (work.Keys[0].Type == Aas.KeyTypes.AssetAdministrationShell) - fi = PackageCentral.Repositories.FindByAasId(work.Keys[0].Value.Trim()); + fi = await PackageCentral.Repositories.FindByAasId(work.Keys[0].Value.Trim()); var boInfo = await LoadFromFileRepository(fi, work); bo = boInfo?.BusinessObject; @@ -2877,7 +2892,7 @@ private async Task ButtonHistory_ObjectRequested(object sender, VisualElementHis && hi.ReferableReference != null) { // try lookup file in file repository - var fi = PackageCentral.Repositories.FindByAasId(hi.ReferableAasId.Trim()); + var fi = await PackageCentral.Repositories.FindByAasId(hi.ReferableAasId.Trim()); if (fi == null) { Log.Singleton.Info( diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 071f5b41e..f9d388a4c 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,9 +9,9 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -// Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); -// Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); Tool("createrepofromapi", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository"); +// Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); +Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); // Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); diff --git a/src/AasxPackageLogic/IMainWindow.cs b/src/AasxPackageLogic/IMainWindow.cs index add90a057..ae1d74a58 100644 --- a/src/AasxPackageLogic/IMainWindow.cs +++ b/src/AasxPackageLogic/IMainWindow.cs @@ -139,7 +139,9 @@ void UiLoadPackageWithNew( public Task UiSearchRepoAndExtendEnvironmentAsync( AdminShellPackageEnvBase packEnv, - Aas.IReference workRef); + Aas.IReference workRef = null, + string fullItemLocation = null, + bool trySelect = false); /// /// Check for menu switch and flush events, if required. diff --git a/src/AasxPackageLogic/MainWindowHeadless.cs b/src/AasxPackageLogic/MainWindowHeadless.cs index cb585ed47..e04859d5a 100644 --- a/src/AasxPackageLogic/MainWindowHeadless.cs +++ b/src/AasxPackageLogic/MainWindowHeadless.cs @@ -1610,7 +1610,7 @@ record = o; else if (ticket["AAS"] is string aasid) { - var ri = PackageCentral.Repositories.FindByAasId(aasid); + var ri = await PackageCentral.Repositories.FindByAasId(aasid); if (ri == null) { LogErrorToTicket(ticket, "Repo Query: AAS-Id not found"); @@ -1621,7 +1621,7 @@ record = o; else if (ticket["Asset"] is string aid) { - var ri = PackageCentral.Repositories.FindByAssetId(aid); + var ri = await PackageCentral.Repositories.FindByAssetId(aid); if (ri == null) { LogErrorToTicket(ticket, "Repo Query: Asset-Id not found"); @@ -1649,11 +1649,41 @@ record = o; var rf = new Aas.Reference(ReferenceTypes.ExternalReference, (new Aas.IKey[] { new Aas.Key(KeyTypes.GlobalReference, uc.ResultId) }).ToList()); - var rres = await MainWindow?.UiSearchRepoAndExtendEnvironmentAsync(PackageCentral.Main, rf); + var rres = await MainWindow?.UiSearchRepoAndExtendEnvironmentAsync( + PackageCentral.Main, rf, trySelect: true); if (rres != null) return; } + // try the remote registries + if (uc.ResultId != null) + { + var ri = await PackageCentral.Repositories.FindByAssetId(uc.ResultId); + if (ri?.Location?.HasContent() == true) + { + // check if load fresh or aggregate + if (PackageCentral.Main is AdminShellPackageDynamicFetchEnv) + { + // load aggregate + Log.Singleton.Info("Aggregating location {0} ..", ri.Location); + var res = await MainWindow?.UiSearchRepoAndExtendEnvironmentAsync( + PackageCentral.Main, + fullItemLocation: ri.Location, + trySelect: true); + if (res != null) + return; + } + else + { + // load + Log.Singleton.Info("Switching to location {0} ..", ri.Location); + MainWindow?.UiLoadPackageWithNew( + PackageCentral.MainItem, null, ri.Location, onlyAuxiliary: false); + return; + } + } + } + // got an file repo item? if (uc.ResultItem != null) lambda(uc.ResultItem); diff --git a/src/AasxPackageLogic/PackageCentral/IPackageContainerFind.cs b/src/AasxPackageLogic/PackageCentral/IPackageContainerFind.cs index 5fc15a4e1..a4d7aedea 100644 --- a/src/AasxPackageLogic/PackageCentral/IPackageContainerFind.cs +++ b/src/AasxPackageLogic/PackageCentral/IPackageContainerFind.cs @@ -9,6 +9,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace AasxPackageLogic.PackageCentral { @@ -19,8 +20,8 @@ namespace AasxPackageLogic.PackageCentral /// public interface IPackageContainerFind { - PackageContainerRepoItem FindByAssetId(string aid); - PackageContainerRepoItem FindByAasId(string aid); + Task FindByAssetId(string aid); + Task FindByAasId(string aid); IEnumerable EnumerateItems(); bool Contains(PackageContainerRepoItem fi); } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index de8cbe43d..58be58bb0 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -160,6 +160,12 @@ public static bool IsValidUriForRepoAllAAS(string location) { var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells(|/|/?\?(.*))$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + // prevent the false alarm for looking for single assetIds + if (m.Success && location.Contains("assetId")) + return false; + + // ok? return m.Success; } @@ -887,7 +893,7 @@ public static async Task LoadFromSourceInternalAsync( // ok operationFound = true; - // prepare receiving the descriptors + // prepare receiving the descriptors/ ids var resObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( new Uri(fullItemLocation), runtimeOptions, allowFakeResponses); @@ -1057,6 +1063,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // start with a list of AAS or Submodels (very similar) var isAllAAS = IsValidUriForRepoAllAAS(fullItemLocation); var isAllSM = IsValidUriForRepoAllSubmodel(fullItemLocation); + var receivedAllAAS = 0; if (!operationFound && (isAllAAS || isAllSM)) { // ok @@ -1128,6 +1135,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( added = prepSM.AddIfNew( idf as Aas.ISubmodel, si); + receivedAllAAS++; trackLoadedIdentifiables?.Add(idf); if (added) trackNewIdentifiables?.Add(idf); @@ -1150,6 +1158,65 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( runtimeOptions?.Log?.Error(ex, "Parsing list of all AAS"); } }); + + } + + // "heal" the GetAllAAS operation from above? + // Means, modify query to lookup + if (isAllAAS && (receivedAllAAS < 1) + && fullItemLocation.Contains("/shells")) + { + // modify query string to provide a list of aas + var filNew = fullItemLocation.Replace("/shells", "/lookup/shells"); + + // prepare receiving the descriptors/ ids + var resObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( + new Uri(filNew), runtimeOptions, allowFakeResponses); + + if (resObj == null) + { + runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + return null; + } + + // Have a list of ids. Decompose into single id. + // Note: Parallel makes no sense, ideally only 1 result (is per AssetId)!! + // TODO: not parallel! + var noRes = true; + foreach (var res in resObj) + { + noRes = false; + + // in res, have only an id. Get the AAS itself + var aas = await PackageHttpDownloadUtil.DownloadIdentifiableToOK( + BuildUriForRepoSingleAAS(baseUri, "" + res, encryptIds: true), + runtimeOptions, allowFakeResponses); + + // found? + if (aas != null) + { + // add + trackLoadedIdentifiables?.Add(aas); + if (prepAas.AddIfNew(aas, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = aas.Id, + IdShort = aas.IdShort, + Endpoint = new Uri(fullItemLocation) + })) + { + trackNewIdentifiables?.Add(aas); + } + } + } + + // check again (count) + if (noRes) + { + runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + return null; + } } // start with single AAS? diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs index fef6274f8..b913afa3e 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs @@ -54,6 +54,11 @@ public class PackageContainerListBase : IPackageContainerFind public ObservableCollection FileMap = new ObservableCollection(); + /// + /// If true, will respond on querying. + /// + public bool ToBeQueried = true; + /// /// Length of the fading effect of animations in [sec] /// @@ -64,6 +69,7 @@ public class PackageContainerListBase : IPackageContainerFind /// True if it represents a finalized list of items. /// False e.g. for a Repository or Regeistry, where the count of items varies. /// + [JsonIgnore] public bool IsStaticList = true; /// @@ -120,8 +126,11 @@ public void MoveDown(PackageContainerRepoItem fi) // IFindRepo interface // - public PackageContainerRepoItem FindByAssetId(string aid) + public virtual async Task FindByAssetId(string aid) { + await Task.Yield(); + if (!ToBeQueried) + return null; return this.FileMap?.FirstOrDefault((fi) => { foreach (var id in fi.EnumerateAssetIds()) @@ -131,8 +140,11 @@ public PackageContainerRepoItem FindByAssetId(string aid) }); } - public PackageContainerRepoItem FindByAasId(string aid) + public virtual async Task FindByAasId(string aid) { + await Task.Yield(); + if (!ToBeQueried) + return null; return this.FileMap?.FirstOrDefault((fi) => { foreach (var id in fi.EnumerateAasIds()) diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs index 6db718efa..e9dcd8292 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs @@ -170,5 +170,40 @@ public override string GetFullItemLocation(string location) throw new NotImplementedException("AasxFileRepoHttpRestRepository.GetFullItemLocation()"); } + public override async Task FindByAssetId(string aid) + { + if (!ToBeQueried) + return null; + + try + { + // try to do this natively + var fil = PackageContainerHttpRepoSubset.BuildUriForRegistryAasByAssetLink(Endpoint, aid); + + // prepare receiving the descriptors/ ids + var idsObj = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject( + fil, runtimeOptions: null); + + // Note: GetAllAssetAdministrationShellIdsByAssetLink only returns a list of ids + if (idsObj == null) + return null; + + // Have a list of ids. Decompose into single id. + // Note: Parallel makes no sense, ideally only 1 result (is per AssetId)!! + foreach (var id in idsObj) { + var ri = new PackageContainerRepoItem() + { + Location = PackageContainerHttpRepoSubset.BuildUriForRepoSingleAAS(Endpoint, "" + id)?.ToString() + }; + return ri; + } + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + return null; + } + } } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListOfList.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListOfList.cs index a35a72843..cc4a1d82d 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListOfList.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListOfList.cs @@ -45,22 +45,24 @@ public PackageContainerListBase AddAtTop(PackageContainerListBase el) // IRepoFind interface // - public PackageContainerRepoItem FindByAssetId(string aid) + public async Task FindByAssetId(string aid) { + await Task.Yield(); foreach (var fr in this) { - var fi = fr?.FindByAssetId(aid); + var fi = await fr?.FindByAssetId(aid); if (fi != null) return fi; } return null; } - public PackageContainerRepoItem FindByAasId(string aid) + public async Task FindByAasId(string aid) { + await Task.Yield(); foreach (var fr in this) { - var fi = fr?.FindByAasId(aid); + var fi = await fr?.FindByAasId(aid); if (fi != null) return fi; } diff --git a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml index 5c8f65e71..b510aa9f0 100644 --- a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml +++ b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml @@ -273,10 +273,10 @@ - + - + diff --git a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml.cs b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml.cs index 9f029586f..563b7f4ec 100644 --- a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml.cs +++ b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListControl.xaml.cs @@ -171,7 +171,6 @@ private void ImageManyIcons_MouseDown(object sender, MouseButtonEventArgs e) } } - private async void Button_Click(object sender, RoutedEventArgs e) { if (sender == this.ButtonQuery) diff --git a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs index 379e61b4d..c6f6e4780 100644 --- a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs +++ b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs @@ -217,6 +217,11 @@ public async Task CommandBinding_FileRepoAll(Control senderList, PackageContaine } } + if (cmd == "filerepoenabletoquery") + { + fr.ToBeQueried = !fr.ToBeQueried; + } + if (cmd == "filerepoloadallresident") if (fr is PackageContainerListLocalBase frlb && !(fr is PackageContainerListLastRecentlyUsed)) @@ -463,12 +468,15 @@ private async Task PackageContainerListControl_ButtonClick( .AddAction("FileRepoSaveAs", "Save as ..", icon: "\U0001f4be") .AddSeparator(); + menu.AddAction("FileRepoEnableToQuery", + "Enable to be queried ..", icon: "\u26cb", isChecked: fr.ToBeQueried); + if (!(fr is PackageContainerListLastRecentlyUsed)) { menu.AddAction( "FileRepoLoadAllResident", "Load all resident files ..", icon: "\U0001f503"); - if (fr is PackageContainerListLocal) + if (fr is PackageContainerListLocal) { menu.AddAction( "FileRepoMakeRelative", "Make AASX filenames relative ..", icon: "\u2699"); diff --git a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs index 58cf484d7..8e099bac5 100644 --- a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs +++ b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs @@ -510,7 +510,9 @@ protected async Task LoadFromFileRepository(PackageC public async Task UiSearchRepoAndExtendEnvironmentAsync( AdminShellPackageEnvBase packEnv, - Aas.IReference workRef) + Aas.IReference workRef = null, + string fullItemLocation = null, + bool tryDisplay = false) { await Task.Yield(); @@ -559,9 +561,9 @@ public async Task UiHandleNavigateTo( // find? PackageContainerRepoItem fi = null; if (work.Keys[0].Type == Aas.KeyTypes.GlobalReference) //TODO (jtikekar, 0000-00-00): KeyTypes.AssetInformation - fi = PackageCentral.Repositories.FindByAssetId(work.Keys[0].Value.Trim()); + fi = await PackageCentral.Repositories.FindByAssetId(work.Keys[0].Value.Trim()); if (work.Keys[0].Type == Aas.KeyTypes.AssetAdministrationShell) - fi = PackageCentral.Repositories.FindByAasId(work.Keys[0].Value.Trim()); + fi = await PackageCentral.Repositories.FindByAasId(work.Keys[0].Value.Trim()); var boInfo = await LoadFromFileRepository(fi, work); bo = boInfo?.BusinessObject; From 8662edc88a8cc252ff717d6fa41bfa2fab847ff7 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 18 Nov 2024 23:34:03 +0100 Subject: [PATCH 33/99] * update --- src/AasxPackageExplorer/MainWindow.xaml.cs | 16 +++++++--- src/AasxPackageExplorer/debug.MIHO.script | 4 +-- .../options-debug.MIHO.json | 6 +++- src/AasxPackageLogic/AnyUiforAas.cs | 2 ++ .../MainWindowAnyUiDialogs.cs | 4 +-- src/AasxPackageLogic/Options.cs | 9 +++--- .../PackageContainerListBase.cs | 32 ++++++++++++------- .../PackageContainerListFactory.cs | 13 ++++++-- .../PackageContainerListHttpRestRepository.cs | 5 +-- .../PackageContainerListLocal.cs | 11 +++++-- .../SelectFromRepositoryFlyout.xaml.cs | 4 +++ src/BlazorExplorer/Data/BlazorSession.cs | 16 +++++++--- .../AnyUiFlyoutSelectFromRepository.razor | 8 +++-- 13 files changed, 91 insertions(+), 39 deletions(-) diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index aec9e6f2b..4b2ed6385 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -986,13 +986,19 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) } // Repository pointed by the Options - if (Options.Curr.AasxRepositoryFn.HasContent()) + if (Options.Curr.AasxRepositoryFns != null && Options.Curr.AasxRepositoryFns.Count > 0) { - var fr2 = await Logic.UiLoadFileRepositoryAsync(Options.Curr.AasxRepositoryFn, tryLoadResident: true); - if (fr2 != null) + foreach (var arf in Options.Curr.AasxRepositoryFns) { - this.UiShowRepositories(visible: true); - PackageCentral.Repositories.AddAtTop(fr2); + if (arf?.HasContent() != true) + continue; + + var fr2 = await Logic.UiLoadFileRepositoryAsync(arf, tryLoadResident: true); + if (fr2 != null) + { + this.UiShowRepositories(visible: true); + PackageCentral.Repositories.AddAtTop(fr2); + } } } diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index f9d388a4c..cb304c0eb 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -9,9 +9,9 @@ // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); -Tool("createrepofromapi", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository"); +// Tool("createrepofromapi", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository"); // Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); -Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); +// Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); // Tool("connectextended", "BaseAddress", "https://techday2-registry.admin-shell-io.com/", "BaseType", "Registry", "GetSingleAas", "false", "GetAasByAssetLink", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AssetId", "https://pk.harting.com/?.20P=ZSN1"); // Select("AAS", "First"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 3e301b5b2..4236bdd30 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -70,7 +70,11 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\aid\\robotic_cell_for_demo_suitcase_new-v3.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02006-2-1_Template_Digital Nameplate_V3_Demo_ExportSMT - Kopie.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_play_02.aasx", - "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", + "AasxRepositoryFns": [ + "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", + "C:\\HOMI\\Develop\\Aasx\\repo\\local-5001.json" + ], + // "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo\\local-5001.json", // "AasxRepositoryFn": "C:\\Users\\homi0002\\Desktop\\test3\\new-aasx-repo.json" , // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\smt_pcn\\SMT_ProductChangeNotification_Draft_play_02.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\dnp30\\IDTA 02006-3-0_Template_Digital Nameplate_AsciiDoc_Draft_v06.aasx", diff --git a/src/AasxPackageLogic/AnyUiforAas.cs b/src/AasxPackageLogic/AnyUiforAas.cs index 38e8923ea..0531d8458 100644 --- a/src/AasxPackageLogic/AnyUiforAas.cs +++ b/src/AasxPackageLogic/AnyUiforAas.cs @@ -287,6 +287,7 @@ public AnyUiDialogueDataSelectFromRepository( { } +#if __old public PackageContainerRepoItem SearchId(string aid) { // condition @@ -309,6 +310,7 @@ public PackageContainerRepoItem SearchId(string aid) return null; } +#endif } public class AnyUiDialogueDataSelectQualifierPreset : AnyUiDialogueDataBase diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index ea63ec81f..ecb51e883 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -1973,7 +1973,7 @@ public PackageContainerListBase UiLoadFileRepository(string fn) catch (Exception ex) { Log.Singleton.Error( - ex, $"When loading aasx file repository {Options.Curr.AasxRepositoryFn}"); + ex, $"When loading aasx file repository {Options.Curr.AasxRepositoryFns}"); } return null; @@ -2007,7 +2007,7 @@ public async Task UiLoadFileRepositoryAsync(string fn, catch (Exception ex) { Log.Singleton.Error( - ex, $"When loading aasx file repository {Options.Curr.AasxRepositoryFn}"); + ex, $"When loading aasx file repository {Options.Curr.AasxRepositoryFns}"); } return null; diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index f06af2956..b23c137fd 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -396,10 +396,10 @@ public class OptionsInformation Cmd = "-dataspecpreset", Arg = "")] public string DataSpecPresetFile = null; - [OptionDescription(Description = "Path to a JSON, defining a set of AasxPackage-Files, which serve as " + - "repository", + [OptionDescription(Description = "List of pathes to a JSON, defining a set of AasxPackage-Files, " + + "which serve as repository.", Cmd = "-aasxrepo", Arg = "")] - public string AasxRepositoryFn = null; + public List AasxRepositoryFns = null; [OptionDescription(Description = "Home address of the content browser on startup, on change of AASX", Cmd = "-contenthome", Arg = "")] @@ -896,7 +896,8 @@ public static void ParseArgs(string[] args, OptionsInformation optionsInformatio } if (arg == "-aasxrepo" && morearg > 0) { - optionsInformation.AasxRepositoryFn = args[index + 1]; + optionsInformation.AasxRepositoryFns = new List(); + optionsInformation.AasxRepositoryFns.Add(args[index + 1]); index++; continue; } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs index b913afa3e..253c1e135 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListBase.cs @@ -210,7 +210,7 @@ public virtual string GetFullItemLocation(string location) return null; } - protected JsonSerializerSettings GetSerializerSettings() + protected static JsonSerializerSettings GetSerializerSettings() { // need special settings (to handle different typs of child classes of PackageContainer) var settings = AasxPluginOptionSerialization.GetDefaultJsonSettings( @@ -224,7 +224,7 @@ public void SaveAsLocalFile(string fn) using (var s = new StreamWriter(fn)) { var settings = GetSerializerSettings(); - settings.TypeNameHandling = TypeNameHandling.Auto; + settings.TypeNameHandling = TypeNameHandling.Objects; var json = JsonConvert.SerializeObject(this, Formatting.Indented, settings); s.WriteLine(json); } @@ -350,20 +350,28 @@ public static PackageContainerListBase CreateDemoData() return tr; } - public bool LoadFromLocalFile(string fn) + public static T LoadFromLocalFile(string fn) where T : PackageContainerListBase { - // from file - if (!System.IO.File.Exists(fn)) - return false; + try + { + // from file + if (!System.IO.File.Exists(fn)) + return null; - // need special settings (to handle different typs of child classes of PackageContainer) - var settings = GetSerializerSettings(); + // need special settings (to handle different typs of child classes of PackageContainer) + var settings = GetSerializerSettings(); - var init = System.IO.File.ReadAllText(fn); - JsonConvert.PopulateObject(init, this, settings); + var init = System.IO.File.ReadAllText(fn); + var res = JsonConvert.DeserializeObject(init, settings); - // return - return true; + // return + return res; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + return null; } // diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListFactory.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListFactory.cs index 103341deb..a648291bc 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListFactory.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListFactory.cs @@ -42,8 +42,17 @@ public static PackageContainerListBase GuessAndCreateNew(string location) return repo; } - // default - return PackageContainerListLocal.Load(location); + // try load from stored type + var res = PackageContainerListLocal.Load(location); + + // if not successfull or "too base", try local + if (res == null || res.GetType() == typeof(PackageContainerListBase)) + { + res = PackageContainerListLocal.Load(location); + } + + // ok + return res; } } } diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs index e9dcd8292..090398ae9 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListHttpRestRepository.cs @@ -34,15 +34,16 @@ public class PackageContainerListHttpRestRepository : PackageContainerListHttpRe private PackageConnectorHttpRest _connector; + [JsonIgnore] public string ServerStatus { get; private set; } = "Status unknown!"; + [JsonIgnore] public readonly PackCntRuntimeOptions CentralRuntimeOptions; /// /// REST endpoint of the AAS repository, that is, without /shells etc. but /// with e.g. /api/v3.0/ /// - [JsonIgnore] public Uri Endpoint; // @@ -59,7 +60,7 @@ public PackageContainerListHttpRestRepository(string location, PackCntRuntimeOpt CentralRuntimeOptions = centralRuntimeOptions; // always have a location - Endpoint = new Uri(location); + Endpoint = (location == null) ? null : new Uri(location); // directly set endpoint // Note: later diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerListLocal.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerListLocal.cs index f9269032c..ec751b942 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerListLocal.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerListLocal.cs @@ -48,15 +48,22 @@ public void SaveAs(string fn) this.Filename = fn; } - public static T Load(string fn) where T : PackageContainerListLocalBase, new() + public static T Load(string fn) where T : PackageContainerListBase, new() { // make sub type, but populate base type +#if __old var repo = new T(); if (!repo.LoadFromLocalFile(fn)) return null; +#else + var repo = PackageContainerListBase.LoadFromLocalFile(fn); + if (repo == null) + return null; +#endif // record - repo.Filename = fn; + if (repo is PackageContainerListLocalBase rlb) + rlb.Filename = fn; // return return repo; diff --git a/src/AasxWpfControlLibrary/Flyouts/SelectFromRepositoryFlyout.xaml.cs b/src/AasxWpfControlLibrary/Flyouts/SelectFromRepositoryFlyout.xaml.cs index ce43697ac..57fb6a3bb 100644 --- a/src/AasxWpfControlLibrary/Flyouts/SelectFromRepositoryFlyout.xaml.cs +++ b/src/AasxWpfControlLibrary/Flyouts/SelectFromRepositoryFlyout.xaml.cs @@ -136,6 +136,9 @@ private void ButtonOk_Click(object sender, RoutedEventArgs e) { // search var assid = TextBoxAssetId.Text; + + // The following is replaced by FindByAssetId.. in repository search +#if __old var ri = DiaData?.SearchId(assid); if (ri != null) { @@ -145,6 +148,7 @@ private void ButtonOk_Click(object sender, RoutedEventArgs e) ControlClosed?.Invoke(); return; } +#endif // use the asset id only if (assid.HasContent()) diff --git a/src/BlazorExplorer/Data/BlazorSession.cs b/src/BlazorExplorer/Data/BlazorSession.cs index 0384169b5..38251ca07 100644 --- a/src/BlazorExplorer/Data/BlazorSession.cs +++ b/src/BlazorExplorer/Data/BlazorSession.cs @@ -262,13 +262,19 @@ public BlazorSession() } // Repository pointed by the Options - if (Options.Curr.AasxRepositoryFn.HasContent()) + if (Options.Curr.AasxRepositoryFns != null && Options.Curr.AasxRepositoryFns.Count > 0) { - var fr2 = Logic.UiLoadFileRepository(Options.Curr.AasxRepositoryFn); - if (fr2 != null) + foreach (var arf in Options.Curr.AasxRepositoryFns) { - PackageCentral.Repositories ??= new PackageContainerListOfList(); - PackageCentral.Repositories.AddAtTop(fr2); + if (arf?.HasContent() != true) + continue; + + var fr2 = Logic.UiLoadFileRepository(arf); + if (fr2 != null) + { + PackageCentral.Repositories ??= new PackageContainerListOfList(); + PackageCentral.Repositories.AddAtTop(fr2); + } } } diff --git a/src/BlazorExplorer/Pages/AnyUiFlyoutSelectFromRepository.razor b/src/BlazorExplorer/Pages/AnyUiFlyoutSelectFromRepository.razor index 72ecc960b..e6c56d7c3 100644 --- a/src/BlazorExplorer/Pages/AnyUiFlyoutSelectFromRepository.razor +++ b/src/BlazorExplorer/Pages/AnyUiFlyoutSelectFromRepository.razor @@ -101,12 +101,16 @@ if (!(DialogueData is AnyUiDialogueDataSelectFromRepository ddsfr)) return; + // replaced by FindByAssetId.. in repository search +#if __old var ri = ddsfr.SearchId(_assetId); if (ri != null) { - ddsfr.ResultItem = ri; - LeaveResult(true); + ddsfr.ResultItem = ri; } +#endif + + LeaveResult(true); } public void LeaveResult(bool result) From e6d6bcb90acbe727cf0cfe7457478590ce6f53b4 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 23 Dec 2024 12:06:15 +0100 Subject: [PATCH 34/99] * update before refactor ProgressChanged --- .../AdminShellCollections.cs | 10 +++ .../Extensions/ExtendEnvironment.cs | 15 ++++ .../AasxPluginOptionsBase.cs | 17 +++- src/AasxPackageExplorer/App.xaml.cs | 1 - .../MainWindow.CommandBindings.cs | 81 +++++++++++++++++- src/AasxPackageExplorer/MainWindow.xaml.cs | 77 ++++++++++++----- src/AasxPackageExplorer/debug.MIHO.script | 1 + .../options-debug.MIHO.json | 8 +- src/AasxPackageLogic/DispEditHelperModules.cs | 4 + .../DispEditHelperSammModules.cs | 2 +- src/AasxPackageLogic/ExplorerMenuFactory.cs | 4 +- src/AasxPackageLogic/IExecuteMainCommand.cs | 13 +++ src/AasxPackageLogic/Options.cs | 8 +- .../PackageCentral/PackageContainerFactory.cs | 5 ++ .../PackageContainerHttpRepoSubset.cs | 83 ++++++++++++++++++- src/AasxPackageLogic/Plugins.cs | 4 + src/AasxPackageLogic/VisualAasxElements.cs | 39 ++++++++- .../PackageContainerListOfListControl.xaml.cs | 66 ++++++++++++--- 18 files changed, 391 insertions(+), 47 deletions(-) create mode 100644 src/AasxPackageLogic/IExecuteMainCommand.cs diff --git a/src/AasxCsharpLibrary/AdminShellCollections.cs b/src/AasxCsharpLibrary/AdminShellCollections.cs index b93c0f032..838211ff9 100644 --- a/src/AasxCsharpLibrary/AdminShellCollections.cs +++ b/src/AasxCsharpLibrary/AdminShellCollections.cs @@ -39,6 +39,16 @@ public void Add(K key, V value) dict.Add(key, new List { value }); } + public void AddIfValueIsNew(K key, V value) + { + if (dict.TryGetValue(key, out var list)) + { + if (!list.Contains(value)) + list.Add(value); + } + else + dict.Add(key, new List { value }); + } public void Remove(K key) { if (dict.ContainsKey(key)) diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index d8670225f..bd37a9999 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -1071,6 +1071,21 @@ public static IEnumerable FindAllSemanticIdsForAas( return refs; } + /// + /// Warning: very inefficient! + /// + public static IEnumerable FindAllReferencedSemanticIds( + this AasCore.Aas3_0.IEnvironment env) + { + // unique set of references + var refs = new List(); + + foreach (var aas in env.AllAssetAdministrationShells()) + refs.AddRange(env.FindAllSemanticIdsForAas(aas)); + + return refs; + } + /// /// Warning: very inefficient! /// diff --git a/src/AasxIntegrationBase/AasxPluginOptionsBase.cs b/src/AasxIntegrationBase/AasxPluginOptionsBase.cs index c1fde47e9..d9f84b956 100644 --- a/src/AasxIntegrationBase/AasxPluginOptionsBase.cs +++ b/src/AasxIntegrationBase/AasxPluginOptionsBase.cs @@ -23,6 +23,20 @@ This source code may use other Open Source software components (see LICENSE.txt) namespace AasxIntegrationBase { + + /// + /// With a lot of reluctance, this forms a global variable to be exchanged among all plugins. + /// Note: as not all plugins do have default options, this is the only way to make plugin + /// behaviour configurable by main application. + /// + public static class AasxPluginsGlobal + { + /// + /// Only check the value (id) field of keys of semanticId for Submodels. + /// + public static bool SubmodelCheckOnlyId = false; + } + /// /// Base class for an options record. This is a piece of options information, which is /// associated with an id of a Submodel template. @@ -249,13 +263,12 @@ public AasxPluginLookupOptionsBase( } #endif - private string GenerateIndexKey(Aas.IKey key) { if (key == null) return null; var k = new Aas.Key(key.Type, key.Value); - var ndx = k?.ToStringExtended(); + var ndx = k?.ToStringExtended(format: AasxPluginsGlobal.SubmodelCheckOnlyId ? 2 : 0); return ndx; } diff --git a/src/AasxPackageExplorer/App.xaml.cs b/src/AasxPackageExplorer/App.xaml.cs index be754d243..743934bf2 100644 --- a/src/AasxPackageExplorer/App.xaml.cs +++ b/src/AasxPackageExplorer/App.xaml.cs @@ -88,7 +88,6 @@ public static OptionsInformation InferOptions(string exePath, string[] args) var loadedPlugins = Plugins.TryActivatePlugins(pluginDllInfos); Plugins.TrySetOptionsForPlugins(pluginDllInfos, loadedPlugins); - return loadedPlugins; } diff --git a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs index 2d8c19e2f..916f8b156 100644 --- a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs +++ b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs @@ -33,12 +33,12 @@ This source code may use other Open Source software components (see LICENSE.txt) using Aas = AasCore.Aas3_0; namespace AasxPackageExplorer -{ +{ /// /// This partial class contains all command bindings, such as for the main menu, in order to reduce the /// complexity of MainWindow.xaml.cs /// - public partial class MainWindow : Window, IFlyoutProvider + public partial class MainWindow : Window, IFlyoutProvider, IExecuteMainCommand { private string lastFnForInitialDirectory = null; @@ -49,7 +49,6 @@ public partial class MainWindow : Window, IFlyoutProvider //// //// .AddWpf\(name: "\5", header: "\1", inputGesture: "\3", \4\) - public void RememberForInitialDirectory(string fn) { this.lastFnForInitialDirectory = fn; @@ -97,6 +96,82 @@ public void CommandExecution_RedrawAll() /// Set to true, if the application shall be shut down via script /// public bool ScriptModeShutdown = false; + + public async Task ExecuteMainMenuCommand(string menuItemName, params object[] args) + { + if (menuItemName?.HasContent() != true) + { + Log.Singleton.Error("MainWindow execute menu command: menu item name missing!"); + return -1; + } + + // name of tool, find it + var foundMenu = this.GetMainMenu(); + var mi = foundMenu.FindName(menuItemName); + if (mi == null) + { + foundMenu = this.GetDynamicMenu(); + mi = foundMenu.FindName(menuItemName); + } + if (mi == null) + { + Log.Singleton.Error($"MainWindow execute menu command: menu item name invalid: {menuItemName}"); + return -1; + } + + // create a ticket + var ticket = new AasxMenuActionTicket() + { + MenuItem = mi, + ScriptMode = true, + ArgValue = new AasxMenuArgDictionary() + }; + + // go thru the remaining arguments and find arg names and values + var argi = 0; + while (args != null && argi < args.Length) + { + // get arg name + if (!(args[argi] is string argname)) + { + Log.Singleton.Error($"MainWindow execute menu command: Argument at index {argi} is " + + $"not string type for argument name."); + return -1; + } + + // find argname? + var ad = mi.ArgDefs?.Find(argname); + if (ad == null) + { + Log.Singleton.Error($"MainWindow execute menu command: Argument at index {argi} is " + + $"not valid argument name."); + return -1; + } + + // create arg value (not available is okay) + object av = null; + if (argi + 1 < args.Length) + av = args[argi + 1]; + + // into ticket + ticket.ArgValue.Add(ad, av); + + // 2 forward! + argi += 2; + } + + // invoke action + await foundMenu.ActivateAction(mi, ticket); + + // perform UI updates if required + if (ticket.UiLambdaAction != null && !(ticket.UiLambdaAction is AnyUiLambdaActionNone)) + { + // add to "normal" event quoue + this.AddWishForToplevelAction(ticket.UiLambdaAction); + } + + return 0; + } private async Task CommandBinding_GeneralDispatch( string cmd, diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 4b2ed6385..c6592a95d 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -404,6 +404,7 @@ public void UiLoadPackageWithNew( if (info == null) info = loadLocalFilename; Log.Singleton.Info("Loading new AASX from: {0} as auxiliary {1} ..", info, onlyAuxiliary); + if (!packItem.Load(PackageCentral, loadLocalFilename, loadLocalFilename, overrideLoadResident: true, PackageContainerOptionsBase.CreateDefault(Options.Curr))) @@ -965,6 +966,7 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) RepoListControl.PackageCentral = PackageCentral; RepoListControl.FlyoutProvider = this; RepoListControl.ManageVisuElems = DisplayElements; + RepoListControl.ExecuteMainCommand = this; this.UiShowRepositories(visible: false); // event viewer @@ -1011,26 +1013,63 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) if (repo is PackageContainerListHttpRestRepository restRepo) { - var fetchContext = new PackageContainerHttpRepoSubsetFetchContext() + // find a specific location + if (PackageContainerHttpRepoSubset.IsValidUriAnyMatch(fi?.Location)) { - Record = new ConnectExtendedRecord() + // try load that specific location + // check if load fresh or aggregate + if (PackageCentral.Main is AdminShellPackageDynamicFetchEnv) { - BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, - BaseAddress = restRepo.Endpoint?.ToString() + // load aggregate + Log.Singleton.Info("Aggregating location {0} ..", fi.Location); + var res = await UiSearchRepoAndExtendEnvironmentAsync( + PackageCentral.Main, + fullItemLocation: fi.Location, + trySelect: true); + + // error? + if (res == null) + Log.Singleton.Error("Not able to access location {0}", fi.Location); + + // in any case, stop here + return; + } + else + { + // load + Log.Singleton.Info("Switching to location {0} ..", fi.Location); + UiLoadPackageWithNew(PackageCentral.MainItem, null, + fi.Location, onlyAuxiliary: false, preserveEditMode: true); + + // in any case, stop here + return; } - }; - - // refer to (static) function - var res = await DispEditHelperEntities.ExecuteUiForFetchOfElements( - PackageCentral, DisplayContext, - ticket : null, - mainWindow: this, - fetchContext: fetchContext, - preserveEditMode: true, - doEditNewRecord: true, - doCheckTainted: true, - doFetchGoNext: false, - doFetchExec: true); + } + + // if not a specific location is available, display general dialogue + if (true) + { + var fetchContext = new PackageContainerHttpRepoSubsetFetchContext() + { + Record = new ConnectExtendedRecord() + { + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + BaseAddress = restRepo.Endpoint?.ToString() + } + }; + + // refer to (static) function + var res = await DispEditHelperEntities.ExecuteUiForFetchOfElements( + PackageCentral, DisplayContext, + ticket: null, + mainWindow: this, + fetchContext: fetchContext, + preserveEditMode: true, + doEditNewRecord: true, + doCheckTainted: true, + doFetchGoNext: false, + doFetchExec: true); + } } // @@ -1766,7 +1805,7 @@ private void UiHandleReRenderAnyUiInEntityPanel( AdminShellPackageEnvBase packEnv, Aas.IReference workRef = null, string fullItemLocation = null, - bool tryDisplay = false) + bool trySelect = false) { await Task.Yield(); @@ -1841,7 +1880,7 @@ private void UiHandleReRenderAnyUiInEntityPanel( var newIdf = newIdfs.FirstOrDefault(); // display - if (tryDisplay) + if (trySelect) { var veFound = this.DisplayElements.SearchVisualElementOnMainDataObject(newIdf, alsoDereferenceObjects: true); if (veFound != null) diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index cb304c0eb..5ef369c66 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -10,6 +10,7 @@ // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "AntoraStyle", "true"); Tool("editkey"); // Tool("createrepofromapi", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository"); +Tool("connectextended", "BaseAddress", "http://smt-repo.admin-shell-io.com/api/v3.0", "BaseType", "Repository", "AasId", "https://admin-shell.io/idta/aas/DigitalNameplate/3/0", "AutoLoadOnDemand", "false", "AutoLoadCds", "true"); // Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "false", "GetAllAas", "true", "PageLimit", "99", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "http://localhost:5001/api/v3.0/", "BaseType", "Repository", "GetSingleAas", "true", "AasId", "http://smart.festo.com/id/demo-box/aas/instance/99920202206560529000071817", "AutoLoadOnDemand", "false"); // Tool("connectextended", "BaseAddress", "https://eis-data.aas-voyager.com/", "BaseType", "Repository", "GetSingleAas", "true", "PageLimit", "2", "AutoLoadOnDemand", "false", "AasId", "https://new.abb.com/products/de/2CSF204101R1400/aas"); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 4236bdd30..e5b8228c4 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -50,10 +50,11 @@ // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\Syn2tecMachine_P2518_AAS__V3_DL2.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\Syn2tecMachine_P2518_AAS__V3_DL2.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\test-data\\MTP\\test-data-SMT_MTP.aasx", + "AasxToLoad": "C:\\Users\\Micha\\Desktop\\Demo\\AASX\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\00_FestoDemoBox-Module-2-Kopie.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\tmp\\8001203_SPAU-P10R-T-R18M-L-PNLK-PNVBA-M8D_060ff64f-9fd2-422d-81ce-b17e49f007c5_work_spiel.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", - "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\1449600_NEBM-SM12G8-E-1.5-Q5-LE6_511946fb-00c1-4aa8-9877-9ba23d86146e.aasx", // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\a33.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v02.aasx", // "AasxToLoad": "C:\\MIHO\\Develop\\Aasx\\repo\\SMT_and_SAMM_Showcase_v01.aasx", @@ -72,7 +73,9 @@ // "AasxToLoad": "C:\\Users\\homi0002\\Desktop\\SMT_ProductChangeNotification\\SMT_ProductChangeNotification_Draft_play_02.aasx", "AasxRepositoryFns": [ "C:\\HOMI\\Develop\\Aasx\\repo_Festo_demo_case_V3\\Festo-DemoCase-repo-V3-local.json", - "C:\\HOMI\\Develop\\Aasx\\repo\\local-5001.json" + "C:\\HOMI\\Develop\\Aasx\\repo\\local-5001.json", + "C:\\HOMI\\Develop\\Aasx\\repo\\eis-voyager.json", + "C:\\HOMI\\Develop\\Aasx\\repo\\smt-repo.json" ], // "AasxRepositoryFn": "C:\\HOMI\\Develop\\Aasx\\repo\\local-5001.json", // "AasxRepositoryFn": "C:\\Users\\homi0002\\Desktop\\test3\\new-aasx-repo.json" , @@ -135,6 +138,7 @@ "VerboseConnect": true, "WorkDir": ".\\work", "CdSortOrder": "Structured", + "SubmodelCheckOnlyId": true, "ObserveEvents": true, "CompressEvents": true, "CheckSmtElements": false, diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 3861be7df..6377efb84 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -1818,6 +1818,10 @@ public void DisplayOrEditEntityDataSpecificationIec61360( return new AnyUiLambdaActionRedrawEntity(); })) { + // TODO (MIHO, 2024-12-09): check if to allow further Iec data types such as "File": + // comboBoxItems: (AdminShellUtil.GetEnumValues() + // .Select((dt) => dt.ToString())).ToArray(), + AddKeyValueExRef( stack, "dataType", dsiec, Aas.Stringification.ToString(dsiec.DataType), null, repo, v => diff --git a/src/AasxPackageLogic/DispEditHelperSammModules.cs b/src/AasxPackageLogic/DispEditHelperSammModules.cs index ebee2e9c2..9450777f6 100644 --- a/src/AasxPackageLogic/DispEditHelperSammModules.cs +++ b/src/AasxPackageLogic/DispEditHelperSammModules.cs @@ -1714,7 +1714,7 @@ public void ImportCreateCDandIds( DispEditHelperSammModules.SammExtensionHelperUpdateJson(newSammExt, si.SammType, si.SammInst); // save CD - env?.ConceptDescriptions?.Add(newCD); + env?.Add(newCD); } public void ImportSammModelToConceptDescriptions( diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index abd9a2447..67b8b96e3 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -122,7 +122,9 @@ public static AasxMenu CreateMainMenu() .AddWpfBlazor(name: "ConnectExtended", header: "Connect (extended) …", args: new AasxMenuListOfArgDefs() .AddFromReflection(new PackageContainerHttpRepoSubset.ConnectExtendedRecord())) - .AddWpfBlazor(name: "ApiUploadAssistant", header: "Upload assistant …") + .AddWpfBlazor(name: "ApiUploadAssistant", header: "Upload assistant …", + args: new AasxMenuListOfArgDefs() + .AddFromReflection(new PackageContainerHttpRepoSubset.UploadAssistantJobRecord())) .AddWpf(name: "CreateRepoFromApi", header: "Create (local) file repository from API base …", args: new AasxMenuListOfArgDefs() .AddFromReflection(new PackageContainerHttpRepoSubset.ConnectExtendedRecord())) diff --git a/src/AasxPackageLogic/IExecuteMainCommand.cs b/src/AasxPackageLogic/IExecuteMainCommand.cs new file mode 100644 index 000000000..75ff7e6c4 --- /dev/null +++ b/src/AasxPackageLogic/IExecuteMainCommand.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AasxPackageLogic +{ + public interface IExecuteMainCommand + { + Task ExecuteMainMenuCommand(string menuItemName, params object[] args); + } +} diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index b23c137fd..d83a77e8b 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -565,7 +565,13 @@ public AnyUiColor GetColor(ColorNames c) Cmd = "-cd-sort-order")] public string CdSortOrder = null; - [OptionDescription(Description = + [OptionDescription(Description = + "Only check the value (id) field of keys of semanticId for Submodels. That is, both" + + "GlobalReference and Submodel types match.", + Cmd = "-sm-only-id")] + public bool SubmodelCheckOnlyId = false; + + [OptionDescription(Description = "For such operations as query repository, do load a new AASX file without " + "prompting the user.", Cmd = "-load-without-prompt")] diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs index 0a1e04d4d..b9769c416 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs @@ -219,6 +219,11 @@ public static async Task GuessAndCreateForAsync( var extCntOpt = new PackageContainerHttpRepoSubsetOptions(containerOptions, new ConnectExtendedRecord()); + // in this situation (guess and load a "complete" ressource), make sure, + // that Submodels are loaded with it .. + extCntOpt.Record.AutoLoadSubmodels = true; + extCntOpt.Record.AutoLoadOnDemand = false; + // prepare runtime options var cnt = await PackageContainerHttpRepoSubset.CreateAndLoadAsync( packageCentral, location, fullItemLocation, diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 58be58bb0..c06644305 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -239,6 +239,9 @@ public static bool IsValidUriForRepoRegistryAasByAssetId(string location) public static bool IsValidUriAnyMatch(string location) { + if (location?.HasContent() != true) + return false; + return IsValidUriForRepoAllAAS(location) || IsValidUriForRepoSingleAAS(location) || IsValidUriForRepoAllSubmodel(location) @@ -1599,6 +1602,76 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( }); }); + // start auto-load missing Submodels? + if (operationFound && (record?.AutoLoadCds ?? false)) + { + var lrs = env.FindAllReferencedSemanticIds().ToList(); + + await Parallel.ForEachAsync(lrs, + new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + async (lr, token) => + { + var cdid = lr.Reference?.GetAsExactlyOneKey()?.Value; + if (cdid?.HasContent() != true) + return; + + if (record?.AutoLoadOnDemand ?? true) + { + // side info level 1 + lock (prepSM) + { + prepSM.AddIfNew(null, new AasIdentifiableSideInfo() + { + IsStub = true, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = lr.Reference.Keys[0].Value, + IdShort = "", + Endpoint = BuildUriForRepoSingleCD(baseUri, cdid) + }); + } + } + else + { + // no side info => full element + var sourceUri = BuildUriForRepoSingleCD(baseUri, cdid); + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + null, + sourceUri: sourceUri, + allowFakeResponses: allowFakeResponses, + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: (code, ms, contentFn) => + { + if (code != HttpStatusCode.OK) + return; + + try + { + var node = System.Text.Json.Nodes.JsonNode.Parse(ms); + var cd = Jsonization.Deserialize.ConceptDescriptionFrom(node); + lock (prepCD) + { + trackLoadedIdentifiables?.Add(cd); + if (prepCD.AddIfNew(cd, new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = cd.Id, + IdShort = cd.IdShort, + Endpoint = sourceUri + })) + { + trackNewIdentifiables?.Add(cd); + } + } + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, "Parsing auto-loaded ConceptDescriptions"); + } + }); + } + }); + } } // @@ -1767,7 +1840,7 @@ public enum BaseTypeEnum { Repository, Registry } "identified by semanticIds as well. " + "Note: For this retrieveal, AutoLoadOnDemand may apply. " + "Note: This might significantly increase the number of retrievals.")] - public bool AutoLoadCds = true; + public bool AutoLoadCds = false; [AasxMenuArgument(help: "When a AAS is retrieved, try to retrieve the associated thumbnail as well.")] public bool AutoLoadThumbnails = true; @@ -2530,11 +2603,15 @@ public static List FindAllUsedFileElements( public class UploadAssistantJobRecord { + [AasxMenuArgument(help: "Specifies the part of the URI of the Repository/ Registry, which is " + + "common to all operations.")] + public string BaseAddress = ""; // public string BaseAddress = "https://cloudrepo.aas-voyager.com/"; - public string BaseAddress = "https://eis-data.aas-voyager.com/"; + // public string BaseAddress = "https://eis-data.aas-voyager.com/"; // public string BaseAddress = "http://smt-repo.admin-shell-io.com/api/v3.0"; // public string BaseAddress = "https://techday2-registry.admin-shell-io.com/"; + [AasxMenuArgument(help: "Either: Repository or Registry")] public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository; // public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Registry; @@ -2604,6 +2681,8 @@ public static async Task PerformUploadAssistant( // var recordJob = new UploadAssistantJobRecord(); + ticket?.ArgValue?.PopulateObjectFromArgs(recordJob); + var ucJob = new AnyUiDialogueDataModalPanel(caption); ucJob.ActivateRenderPanel(recordJob, disableScrollArea: false, diff --git a/src/AasxPackageLogic/Plugins.cs b/src/AasxPackageLogic/Plugins.cs index ecb61a6cc..986b8cf71 100644 --- a/src/AasxPackageLogic/Plugins.cs +++ b/src/AasxPackageLogic/Plugins.cs @@ -189,6 +189,10 @@ public static PluginInstance FindPluginInstance(string pname) public static Dictionary TryActivatePlugins( IReadOnlyList pluginDll) { + // set some global options + AasxPluginsGlobal.SubmodelCheckOnlyId = Options.Curr.SubmodelCheckOnlyId; + + // try load var loadedPlugins = new Dictionary(); for (int index = 0; index < pluginDll.Count; index++) diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index e741dd98c..c82252d66 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -1847,7 +1847,7 @@ private VisualElementConceptDescription GenerateVisualElementsForSingleCD( // remember, that this value pair CD hangs "below" an Submodel if (submodelForCDs != null) { - _cdToSm.Add(vrpCD, submodelForCDs); + _cdToSm.AddIfValueIsNew(vrpCD, submodelForCDs); } } } @@ -1889,8 +1889,8 @@ private VisualElementGeneric GenerateVisualElementsFromShellEnvAddElements( // bookkeeping if (tism.CachedCD != null) { - _cdReferred.Add(tism.CachedCD, tism); - _cdToSm.Add(tism.CachedCD, sm); + _cdReferred.AddIfValueIsNew(tism.CachedCD, tism); + _cdToSm.AddIfValueIsNew(tism.CachedCD, sm); } // nested cd? @@ -2421,6 +2421,37 @@ public void AddVisualElementsFromShellEnv( } } + // MIHO, 2024-12-22: new way to pre-fill _cdToSm + if (true) + { + _cdToSm.Clear(); + + Action lambdaIndex = (sm, semId) => + { + if (semId?.IsValid() != true) + return; + var cdid = semId.GetAsExactlyOneKey()?.Value; + if (cdid == null) + return; + if (!_idToReferable.ContainsKey(cdid)) + return; + var cd = _idToReferable[cdid]?.FirstOrDefault() as Aas.IConceptDescription; + if (cd == null) + return; + _cdToSm.AddIfValueIsNew(cd, sm); + }; + + foreach (var sm in env.AllSubmodels()) + { + lambdaIndex(sm, sm?.SemanticId); + sm?.RecurseOnSubmodelElements(null, (o, parents, sme) => + { + lambdaIndex(sm, sme?.SemanticId); + return true; + }); + } + } + // many operations try { @@ -2649,7 +2680,7 @@ public void AddVisualElementsFromShellEnv( { // single files var files = package.GetListOfSupplementaryFiles(); - foreach (var fi in files) + foreach (var fi in files.ForEachSafe()) tiFiles.Members.Add(new VisualElementSupplementalFile(tiFiles, cache, package, fi)); } } diff --git a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs index c6f6e4780..1fcc8ae02 100644 --- a/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs +++ b/src/AasxWpfControlLibrary/PackageCentral/PackageContainerListOfListControl.xaml.cs @@ -39,6 +39,7 @@ public partial class PackageContainerListOfListControl : UserControl private AasxPackageLogic.PackageCentral.PackageCentral _packageCentral; private IFlyoutProvider _flyout; private IManageVisualAasxElements _manageVisuElems; + private IExecuteMainCommand _executeMainCommand; private PackageContainerListOfList _repoList; /// @@ -57,6 +58,11 @@ public partial class PackageContainerListOfListControl : UserControl /// public IManageVisualAasxElements ManageVisuElems { set { _manageVisuElems = value; } } + /// + /// Handler, which can provide the ability to execute main menu commands. + /// + public IExecuteMainCommand ExecuteMainCommand { set { _executeMainCommand = value; } } + /// /// AasxRepoList which is being managed by this control. Is expected to sit in the PackageCentral. /// Note: only setter, as direct access from outside shall be redirected to the original source. @@ -191,12 +197,30 @@ public async Task CommandBinding_FileRepoAll(Control senderList, PackageContaine // dialogue var uc = new AnyUiDialogueDataSelectFromRepository("Select from: " + fr.Header); uc.Items = fr.EnumerateItems().ToList(); - if (await _flyout?.GetDisplayContext()?.StartFlyoverModalAsync(uc) - && uc.ResultItem != null) + if (await _flyout?.GetDisplayContext()?.StartFlyoverModalAsync(uc)) { - var fi = uc.ResultItem; + // got an asset id only? + if (uc.ResultId != null) + { + var ri = await fr.FindByAssetId(uc.ResultId); + if (ri?.Location?.HasContent() == true) + try + { + // load + Log.Singleton.Info("Switching to repository location {0} ..", ri.Location); + FileDoubleClick?.Invoke(senderList, fr, ri); + } + catch (Exception ex) + { + Log.Singleton.Error( + ex, $"When loading item on repository location {ri.Location}."); + } + } + + // use an item directly + var fi = uc?.ResultItem; var fn = fi?.Location; - if (fn != null) + if (fn?.HasContent() == true) { // start animation fr.StartAnimation(fi, @@ -426,6 +450,13 @@ public async Task CommandBinding_FileRepoAll(Control senderList, PackageContaine } } + if (cmd == "filerepouploadtoapi" && fr is PackageContainerListHttpRestRepository frRepo) + { + await _executeMainCommand?.ExecuteMainMenuCommand( + "ApiUploadAssistant", + "BaseType", "Repository", + "BaseAddress", "" + frRepo.Endpoint?.ToString()); + } } } @@ -473,8 +504,11 @@ private async Task PackageContainerListControl_ButtonClick( if (!(fr is PackageContainerListLastRecentlyUsed)) { - menu.AddAction( - "FileRepoLoadAllResident", "Load all resident files ..", icon: "\U0001f503"); + if (!(fr is PackageContainerListHttpRestBase)) + { + menu.AddAction( + "FileRepoLoadAllResident", "Load all resident files ..", icon: "\U0001f503"); + } if (fr is PackageContainerListLocal) { @@ -482,11 +516,21 @@ private async Task PackageContainerListControl_ButtonClick( "FileRepoMakeRelative", "Make AASX filenames relative ..", icon: "\u2699"); } - menu.AddAction("FileRepoAddCurrent", "Add current AAS", icon: "\u2699") - .AddAction("FileRepoAddToServer", "Add AASX File to File Repository", icon: "\u2699") - .AddAction("FileRepoMultiAdd", "Add multiple AASX files ..", icon: "\u2699") - .AddAction("FileRepoAddFromServer", "Add from REST server ..", icon: "\u2699") - .AddAction("FileRepoPrint", "Print 2D code sheet ..", icon: "\u2699"); + if (fr is PackageContainerListLocalBase) + { + menu.AddAction("FileRepoAddCurrent", "Add current AAS", icon: "\u2699") + .AddAction("FileRepoAddToServer", "Add AASX File to File Repository", icon: "\u2699") + .AddAction("FileRepoMultiAdd", "Add multiple AASX files ..", icon: "\u2699") + .AddAction("FileRepoAddFromServer", "Add from REST server ..", icon: "\u2699") + .AddAction("FileRepoPrint", "Print 2D code sheet ..", icon: "\u2699"); + } + + if (fr is PackageContainerListHttpRestRepository + || fr is PackageContainerListHttpRestRegistry) + { + menu.AddAction( + "FileRepoUploadToApi", "Upload to API ..", icon: "\U0001f879"); + } } var cm2 = DynamicContextMenu.CreateNew( From bb20c1b0c31d9905cec8d308884cee6a5326c6f8 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Mon, 23 Dec 2024 15:10:11 +0100 Subject: [PATCH 35/99] * 1st overall progress working for repo load --- src/AasxPackageExplorer/MainWindow.xaml | 62 +++++++++-- src/AasxPackageExplorer/MainWindow.xaml.cs | 83 ++++++++++---- .../AasxFileServerInterfaceService.cs | 4 +- .../PackageCentral/PackageContainerBase.cs | 11 +- .../PackageContainerHttpRepoSubset.cs | 101 ++++++++++++++++-- .../PackageContainerNetworkHttpFile.cs | 6 +- .../PackageCentral/PackageHttpDownloadUtil.cs | 12 +-- .../ProgressableStreamContent.cs | 12 +-- .../IntegratedConnectFlyout.xaml.cs | 10 +- .../Data/BlazorSession.MainWindow.cs | 5 +- 10 files changed, 240 insertions(+), 66 deletions(-) diff --git a/src/AasxPackageExplorer/MainWindow.xaml b/src/AasxPackageExplorer/MainWindow.xaml index aec553ef9..a9e64fac4 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml +++ b/src/AasxPackageExplorer/MainWindow.xaml @@ -27,8 +27,31 @@ + + + + + + + @@ -71,7 +94,9 @@ - + @@ -215,7 +240,7 @@ - @@ -226,14 +251,14 @@ - - @@ -365,17 +390,40 @@ + + - + + + + + + - - + public bool ExtendedConnectionDebug = false; + + /// + /// Set by the main application in order to be able to cancel an operation + /// Note: Normally, only CancellationTokenSource().Token shall be passed, however this + /// is used as source of truth. + /// + public CancellationTokenSource CancellationTokenSource = null; } /// @@ -370,12 +378,13 @@ public virtual void BackupInDir(string backupDir, int maxFiles, BackupType backu { } - public virtual async Task LoadFromSourceAsync( + public virtual async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) { await Task.Yield(); + return true; } public virtual async Task SaveLocalCopyAsync( diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index a8fb70513..6c3964629 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -128,7 +128,8 @@ public static async Task CreateAndLoadAsync( res.ContainerList = containerList; if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) - await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions); + if (!await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions)) + return null; return res; } @@ -194,9 +195,16 @@ public static bool IsValidUriForRepoSingleSubmodel(string location) return false; } + public static bool IsValidUriForRepoAllCD(string location) + { + var m = Regex.Match(location, @"^(http(|s))://(.*?)/concept-descriptions(|/|/?\?(.*))$", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + return m.Success; + } + public static bool IsValidUriForRepoSingleCD(string location) { - var m = Regex.Match(location, @"^(http(|s))://(.*?)/conceptdescriptions/(.{1,99})$", + var m = Regex.Match(location, @"^(http(|s))://(.*?)/concept-descriptions/(.{1,99})$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } @@ -246,6 +254,7 @@ public static bool IsValidUriAnyMatch(string location) || IsValidUriForRepoSingleAAS(location) || IsValidUriForRepoAllSubmodel(location) || IsValidUriForRepoSingleSubmodel(location) + || IsValidUriForRepoAllCD(location) || IsValidUriForRepoSingleCD(location) || IsValidUriForRepoQuery(location) || IsValidUriForRegistryAllAAS(location) @@ -261,7 +270,7 @@ public static Uri GetBaseUri(string location) // try an explicit search for known parts of ressources // (preserves scheme, host and leading pathes) - var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/conceptdescription|/lookup)"); + var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/concept-description|/lookup)"); if (m.Success) return new Uri(m.Groups[1].ToString() + "/"); @@ -485,6 +494,22 @@ public static Uri BuildUriForRepoSingleSubmodel( encryptIds: encryptIds, usePost: usePost); } + public static Uri BuildUriForRepoAllCD(Uri baseUri, int pageLimit = 100, string cursor = null) + { + // for more info: see BuildUriForRepoAllAAS + // access + if (baseUri == null) + return null; + + var uri = new UriBuilder(CombineUri(baseUri, $"concept-descriptions")); + if (pageLimit > 0) + uri.Query = $"Limit={pageLimit:D}"; + if (cursor != null) + uri.Query += $"&Cursor={cursor}"; + + return uri.Uri; + } + public static Uri BuildUriForRepoSingleCD( Uri baseUri, string id, bool encryptIds = true, @@ -816,7 +841,7 @@ private static async Task FromRegistryGetAasAndSubmodels( return true; } - public override async Task LoadFromSourceAsync( + public override async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) @@ -828,10 +853,17 @@ public override async Task LoadFromSourceAsync( Env, containerOptions: containerOptions, runtimeOptions: runtimeOptions); - if (newEnv != null) - Env = newEnv; + if (newEnv == null) + { + runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.EndOverall, message: "Stopped repo load"); + return false; + } + // okay + Env = newEnv; runtimeOptions?.ProgressChanged?.Invoke(PackCntRuntimeOptions.Progress.EndOverall, message: "Done repo load"); + + return true; } public static async Task LoadFromSourceToTargetAsync( @@ -959,7 +991,7 @@ protected static async Task LoadFromSourceInternalAsyn // Note: GetAllAssetAdministrationShellIdsByAssetLink only returns a list of ids if (resObj == null) { - runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + runtimeOptions?.Log?.Error("Repository/ Registry did not return any AAS descriptors! Aborting."); return null; } @@ -1021,7 +1053,7 @@ await FromRegistryGetAasAndSubmodels( // check again (count) if (noRes) { - runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + runtimeOptions?.Log?.Error("Repository/ Registry did not return any AAS descriptors! Aborting."); return null; } } @@ -1128,11 +1160,12 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // for all repo access, use the same client var client = PackageHttpDownloadUtil.CreateHttpClient(baseUri, runtimeOptions, containerList); - // start with a list of AAS or Submodels (very similar) + // start with a list of AAS or Submodels (very similar, therefore unified) var isAllAAS = IsValidUriForRepoAllAAS(fullItemLocation); var isAllSM = IsValidUriForRepoAllSubmodel(fullItemLocation); + var isAllCD = IsValidUriForRepoAllCD(fullItemLocation); var receivedAllAAS = 0; - if (!operationFound && (isAllAAS || isAllSM)) + if (!operationFound && (isAllAAS || isAllSM || isAllCD)) { // ok operationFound = true; @@ -1156,6 +1189,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( && resChilds.Count > 0) { int childsToSkip = Math.Max(0, record.PageSkip); + int childsRead = 0; bool firstNonSkipped = true; foreach (var n2 in resChilds) @@ -1167,7 +1201,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( { childsToSkip--; continue; - } + } // get identifiable Aas.IIdentifiable idf = null; @@ -1175,6 +1209,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( idf = Jsonization.Deserialize.AssetAdministrationShellFrom(n2); if (isAllSM) idf = Jsonization.Deserialize.SubmodelFrom(n2); + if (isAllCD) + idf = Jsonization.Deserialize.ConceptDescriptionFrom(n2); if (idf == null) continue; @@ -1198,21 +1234,42 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // add var added = false; if (isAllAAS) + { + lambdaReportAll(++numAAS, numSM, numCD, numDiv); added = prepAas.AddIfNew( idf as Aas.IAssetAdministrationShell, si); + } if (isAllSM) + { + lambdaReportAll(numAAS, ++numSM, numCD, numDiv); added = prepSM.AddIfNew( idf as Aas.ISubmodel, si); + } + if (isAllCD) + { + lambdaReportAll(numAAS, numSM, ++numCD, numDiv); + added = prepCD.AddIfNew( + idf as Aas.IConceptDescription, + si); + } receivedAllAAS++; trackLoadedIdentifiables?.Add(idf); if (added) trackNewIdentifiables?.Add(idf); + + // maintain page limit (may be server does not care..) + childsRead++; + if (record.PageLimit > 0 && childsRead >= record.PageLimit) + { + si.ShowCursorBelow = true; + break; + } } catch (Exception ex) { - runtimeOptions?.Log?.Error(ex, "Parsing single AAS/ Submodel of list of all AAS/ Submodel"); + runtimeOptions?.Log?.Error(ex, "Parsing single AAS/ Submodel/ CD of list of all AAS/ Submodel/ CD"); } } @@ -1225,7 +1282,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( } catch (Exception ex) { - runtimeOptions?.Log?.Error(ex, "Parsing list of all AAS"); + runtimeOptions?.Log?.Error(ex, "Parsing list of all AAS / SM / CD"); } }); @@ -1245,7 +1302,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( if (resObj == null) { - runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + runtimeOptions?.Log?.Error("Repository/ Registry did not return any AAS descriptors! Aborting."); return null; } @@ -1287,7 +1344,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // check again (count) if (noRes) { - runtimeOptions?.Log?.Error("Registry did not return any AAS descriptors! Aborting."); + runtimeOptions?.Log?.Error("Repository/ Registry did not return any AAS descriptors! Aborting."); return null; } } @@ -1590,7 +1647,10 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var lrs = env.FindAllSubmodelReferences(onlyNotExisting: true).ToList(); await Parallel.ForEachAsync(lrs, - new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + new ParallelOptions() { + MaxDegreeOfParallelism = record?.ParallelReads ?? Options.Curr.MaxParallelOps, + CancellationToken = runtimeOptions?.CancellationTokenSource?.Token ?? CancellationToken.None + }, async (lr, token) => { if (record?.AutoLoadOnDemand ?? true) @@ -1655,7 +1715,10 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // start auto-load missing thumbnails? if (operationFound && (record?.AutoLoadThumbnails ?? false)) await Parallel.ForEachAsync(env.AllAssetAdministrationShells(), - new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + new ParallelOptions() { + MaxDegreeOfParallelism = record?.ParallelReads ?? Options.Curr.MaxParallelOps, + CancellationToken = runtimeOptions?.CancellationTokenSource?.Token ?? CancellationToken.None + }, async (aas, token) => { await PackageHttpDownloadUtil.HttpGetToMemoryStream( @@ -1686,13 +1749,21 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var lrs = env.FindAllReferencedSemanticIds().ToList(); await Parallel.ForEachAsync(lrs, - new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + new ParallelOptions() { + MaxDegreeOfParallelism = record?.ParallelReads ?? Options.Curr.MaxParallelOps, + CancellationToken = runtimeOptions?.CancellationTokenSource?.Token ?? CancellationToken.None + }, async (lr, token) => { + // cancelled? + token.ThrowIfCancellationRequested(); + + // valid? var cdid = lr.Reference?.GetAsExactlyOneKey()?.Value; if (cdid?.HasContent() != true) return; + // only side info or full? if (record?.AutoLoadOnDemand ?? true) { // side info level 1 @@ -1760,12 +1831,15 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // any operation found? if (!operationFound) { - runtimeOptions?.Log?.Error("Did not found any matching operation in location to " + + runtimeOptions?.Log?.Error("Did not find any matching operation in location to " + "execute on Registry or Repository! Location was: {0}", fullItemLocation); return null; } + // before committing: shall this commit? + runtimeOptions?.CancellationTokenSource?.Token.ThrowIfCancellationRequested(); + // how to commit? if (loadNew) { @@ -1868,10 +1942,10 @@ public enum BaseTypeEnum { Repository, Registry } [AasxMenuArgument(help: "Retrieve all AAS from Repository or Registry. " + "Note: Use of PageLimit is recommended.")] - public bool GetAllAas; + public bool GetAllAas = true; [AasxMenuArgument(help: "Get a single AAS, which is specified by AasId.")] - public bool GetSingleAas = true; + public bool GetSingleAas; [AasxMenuArgument(help: "Specicies the Id of the AAS to be retrieved.")] // public string AasId = "https://new.abb.com/products/de/2CSF204101R1400/aas"; @@ -1896,6 +1970,10 @@ public enum BaseTypeEnum { Repository, Registry } // public string SmId = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ=="; public string SmId = ""; + [AasxMenuArgument(help: "Retrieve all ConceptDescriptions from Repository or Registry. " + + "Note: Use of PageLimit is recommended.")] + public bool GetAllCD; + [AasxMenuArgument(help: "Get a single ConceptDescription, which is specified by CdId.")] public bool GetSingleCD; @@ -1931,6 +2009,9 @@ public enum BaseTypeEnum { Repository, Registry } [AasxMenuArgument(help: "Encrypt given Ids.")] public bool EncryptIds = true; + [AasxMenuArgument(help: "Number of paralle read-operations at the same time. 1 is linear.")] + public int ParallelReads = Options.Curr.MaxParallelOps; + [AasxMenuArgument(help: "Stay connected with Repository/ Registry and eventually subscribe to " + "AAS events.")] public bool StayConnected; @@ -1961,6 +2042,7 @@ public enum QueryChoice { AasByAssetLink, AllSM, SingleSM, + AllCD, SingleCD, Query } @@ -1972,6 +2054,7 @@ public void SetQueryChoices(QueryChoice choice) GetAasByAssetLink = (choice == QueryChoice.AasByAssetLink); GetAllSubmodel = (choice == QueryChoice.AllSM); GetSingleSubmodel = (choice == QueryChoice.SingleSM); + GetAllCD = (choice == QueryChoice.AllCD); GetSingleCD = (choice == QueryChoice.SingleCD); ExecuteQuery = (choice == QueryChoice.Query); } @@ -2002,6 +2085,7 @@ public string GetFetchOperationStr() if (GetAasByAssetLink) res = "GetAllAssetAdministrationShellIdsByAssetLink"; if (GetAllSubmodel) res = "GetAllSubmodels"; if (GetSingleSubmodel) res = "GetSubmodelById"; + if (GetAllCD) res = "GetAllConceptDescriptions"; if (GetSingleCD) res = "GetConceptDescriptionById"; if (ExecuteQuery) res = "ExecuteQuery"; } @@ -2085,6 +2169,13 @@ public static string BuildLocationFrom( return uri?.ToString(); } + // All Submodels? + if (record.GetAllCD) + { + var uri = BuildUriForRepoAllCD(baseUri, record.PageLimit + record.PageSkip, cursor); + return uri?.ToString(); + } + // Single CD? if (record.GetSingleCD) { @@ -2145,7 +2236,7 @@ public enum ConnectExtendedScope { Query = 0x004, GetOptions = 0x0008, StayConnected = 0x0010, - Pagination = 0x0020 + Pagination = 0x0020, } public static async Task PerformConnectExtendedDialogue( @@ -2404,6 +2495,24 @@ public static async Task PerformConnectExtendedDialogue( row += 2; + // All CD + AnyUiUIElement.RegisterControl( + helper.Set( + helper.AddSmallCheckBoxTo(g, row, 0, + content: "Get all CDs", + isChecked: record.GetAllCD, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + colSpan: 2), + (o) => + { + if ((bool)o) + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.AllCD); + else + record.GetAllCD = false; + return new AnyUiLambdaActionModalPanelReRender(uc); + }); + row++; + // Single CD AnyUiUIElement.RegisterControl( helper.Set( @@ -2542,6 +2651,35 @@ public static async Task PerformConnectExtendedDialogue( row++; } + // Parallel execution + if ((scope & ConnectExtendedScope.GetOptions) > 0) + { + // Pagination + helper.AddSmallLabelTo(g, row, 0, content: "Parallel fetch:", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + var g3 = helper.AddSmallGridTo(g, row, 1, 1, 2, new[] { "#", "*" }); + + AnyUiUIElement.SetIntFromControl( + helper.Set( + helper.AddSmallTextBoxTo(g3, 0, 0, + margin: new AnyUiThickness(0, 0, 0, 0), + text: $"{record.ParallelReads:D}", + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + minWidth: 80, maxWidth: 80, + horizontalAlignment: AnyUiHorizontalAlignment.Left), + (i) => { record.ParallelReads = i; }); + + helper.AddSmallLabelTo(g3, 0, 1, content: "(concurrent reads, 1 = sequential)", + margin: new AnyUiThickness(10, 0, 0, 0), + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + row++; + } + if ((scope & ConnectExtendedScope.StayConnected) > 0) { // Stay connected diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs index 9e83f5561..938e32e1d 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerLocalFile.cs @@ -82,7 +82,8 @@ public static async Task CreateAndLoadAsync( packageCentral, location, containerOptions); if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) - await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions); + if (!await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions)) + return null; return res; } @@ -108,7 +109,7 @@ public override string ToString() return s; } - public override async Task LoadFromSourceAsync( + public override async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) @@ -154,6 +155,8 @@ public override async Task LoadFromSourceAsync( } await Task.Yield(); + + return true; } public override async Task SaveToSourceAsync( diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs index 361a3ad9a..fd8edd1d5 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerNetworkHttpFile.cs @@ -93,7 +93,8 @@ public static async Task CreateAndLoadAsync( res.ContainerList = containerList; if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) - await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions); + if (!await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions)) + return null; return res; } @@ -300,7 +301,7 @@ await file.WriteAsync(buffer, 0, bytesRead, } } - public override async Task LoadFromSourceAsync( + public override async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) @@ -329,6 +330,8 @@ public override async Task LoadFromSourceAsync( $"While opening buffered aasx {TempFn} from source {this.ToString()} " + $"at {AdminShellUtil.ShortLocation(ex)} gave: {ex.Message}"); } + + return true; } public override async Task SaveLocalCopyAsync( diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs index 52e978418..56a7dd6ae 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerUserFile.cs @@ -91,7 +91,8 @@ public static async Task CreateAndLoadAsync( packageCentral, location, containerOptions); if (overrideLoadResident || true == res.ContainerOptions?.LoadResident) - await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions); + if (!await res.LoadFromSourceAsync(fullItemLocation, containerOptions, runtimeOptions)) + return null; return res; } @@ -205,7 +206,7 @@ public static IEnumerable EnumerateUserFiles(string searchPattern = null yield return ri; } - public override async Task LoadFromSourceAsync( + public override async Task LoadFromSourceAsync( string fullItemLocation, PackageContainerOptionsBase containerOptions = null, PackCntRuntimeOptions runtimeOptions = null) @@ -254,6 +255,8 @@ public override async Task LoadFromSourceAsync( } await Task.Yield(); + + return true; } public override async Task SaveToSourceAsync(string saveAsNewFileName = null, diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index edf95fe2d..2ac9d3b09 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -762,7 +762,10 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( else { await Parallel.ForEachAsync(entities, - new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + new ParallelOptions() { + MaxDegreeOfParallelism = Options.Curr.MaxParallelOps, + CancellationToken = runtimeOptions?.CancellationTokenSource?.Token ?? CancellationToken.None + }, async (ent, token) => { var thisEnt = ent; @@ -901,7 +904,10 @@ public static async Task DeleteListOfEntities( else { await Parallel.ForEachAsync(entities, - new ParallelOptions() { MaxDegreeOfParallelism = Options.Curr.MaxParallelOps }, + new ParallelOptions() { + MaxDegreeOfParallelism = Options.Curr.MaxParallelOps , + CancellationToken = runtimeOptions?.CancellationTokenSource?.Token ?? CancellationToken.None + }, async (ent, token) => { // delete diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index c82252d66..0a5e7d37b 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -2817,6 +2817,29 @@ public IEnumerable FindAllVisualElementTopToIdentifiable() yield return e; } + public IEnumerable FindAllVisualElementTopInternal(VisualElementGeneric ve) + { + // check if Top category + if (ve is VisualElementEnvironmentItem) + { + yield return ve; + + // recurse, as well + foreach (var child in ve.Members) + foreach (var x in FindAllVisualElementTopInternal(child)) + yield return x; + } + + // if not top, simply die + } + + public IEnumerable FindAllVisualElementTop() + { + foreach (var tvl in this) + foreach (var e in FindAllVisualElementTopInternal(tvl)) + yield return e; + } + public IEnumerable FindAllVisualElementOf(Predicate p) where T : VisualElementGeneric { diff --git a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs index b0d717b07..eb84a2244 100644 --- a/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs +++ b/src/AasxWpfControlLibrary/DiplayVisualAasxElements.xaml.cs @@ -365,6 +365,11 @@ public IEnumerable FindAllVisualElement(Predicate + /// Identifies visual elements, which are *directly* superordinate to Identifiable. + /// Note: Could contain duplicates. + /// + /// public IEnumerable FindAllVisualElementTopToIdentifiable() { if (displayedTreeViewLines != null) @@ -372,6 +377,17 @@ public IEnumerable FindAllVisualElementTopToIdentifiable() yield return ve; } + /// + /// Identifies top visual elements, which are above all content elements + /// + /// + public IEnumerable FindAllVisualElementTop() + { + if (displayedTreeViewLines != null) + foreach (var ve in displayedTreeViewLines.FindAllVisualElementTop()) + yield return ve; + } + public bool Contains(VisualElementGeneric ve) { if (displayedTreeViewLines != null) diff --git a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs index e711e19aa..dace1ba2d 100644 --- a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs +++ b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs @@ -131,7 +131,8 @@ public void UiLoadPackageWithNew( string storeFnToLRU = null, bool indexItems = false, bool preserveEditMode = false, - bool? nextEditMode = null) + bool? nextEditMode = null, + bool autoFocusFirstRelevant = false) { // access if (packItem == null) From ef938db77f15a4154b446c4ee807f6657478ceab Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Wed, 25 Dec 2024 12:23:19 +0100 Subject: [PATCH 37/99] * save as local file --- .../AdminShellPackageEnvBase.cs | 2 +- .../AdminShellPackageFileBasedEnv.cs | 2 +- .../MainWindowAnyUiDialogs.cs | 21 +++++++++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs b/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs index f40980862..92c9b5027 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageEnvBase.cs @@ -257,7 +257,7 @@ public enum SerializationFormat { None, Xml, Json }; public AdminShellPackageEnvBase() { } - public AdminShellPackageEnvBase(AasCore.Aas3_0.Environment env) + public AdminShellPackageEnvBase(AasCore.Aas3_0.IEnvironment env) { if (env != null) _aasEnv = env; diff --git a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs index 21ad10dd4..541a5327e 100644 --- a/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs +++ b/src/AasxCsharpLibrary/AdminShellPackageFileBasedEnv.cs @@ -64,7 +64,7 @@ public class AdminShellPackageFileBasedEnv : AdminShellPackageEnvBase public AdminShellPackageFileBasedEnv() : base() { } - public AdminShellPackageFileBasedEnv(AasCore.Aas3_0.Environment env) : base(env) { } + public AdminShellPackageFileBasedEnv(AasCore.Aas3_0.IEnvironment env) : base(env) { } public AdminShellPackageFileBasedEnv(string fn, bool indirectLoadSave = false) : base() { diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 7560cbc2e..77d477171 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -220,6 +220,7 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( // shall be a local/ user file?! var proposeFn = PackageCentral.MainItem.Filename; + var forceLocal = false; var isLocalFile = PackageCentral.MainItem.Container is PackageContainerLocalFile; var isUserFile = PackageCentral.MainItem.Container is PackageContainerUserFile; if (!isLocalFile && !isUserFile) @@ -233,6 +234,7 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( // point to local directory proposeFn = System.IO.Path.GetFileName(proposeFn); + forceLocal = true; } // filename @@ -248,6 +250,9 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( if (ucsf?.Result != true) return; + // make sure target filename has an extension + var targetFn = ucsf.TargetFileName; + // do try { @@ -260,8 +265,20 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( // save DisplayContextPlus.RememberForInitialDirectory(ucsf.TargetFileName); - await PackageCentral.MainItem.SaveAsAsync(ucsf.TargetFileName, prefFmt: prefFmt, - doNotRememberLocation: ucsf.Location != AnyUiDialogueDataSaveFile.LocationKind.Local); + + if (!forceLocal) + { + // leave it where it is + await PackageCentral.MainItem.SaveAsAsync(targetFn, prefFmt: prefFmt, + doNotRememberLocation: ucsf.Location != AnyUiDialogueDataSaveFile.LocationKind.Local); + } + else + { + // make a temporary new file base environment + var fbEnv = new AdminShellPackageFileBasedEnv(PackageCentral.Main?.AasEnv); + fbEnv.SaveAs(targetFn, writeFreshly: true, prefFmt: prefFmt, saveOnlyCopy: true); + fbEnv.Close(); + } // backup (only for AASX) if (ucsf.FilterIndex == 0) From 5f94093b4ea43ae9090f37c3298e5687b44a9869 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 29 Dec 2024 09:39:34 +0100 Subject: [PATCH 38/99] * upload of attachments --- src/AasxCsharpLibrary/AdminShellUtil.cs | 2 +- src/AasxPackageExplorer/MainWindow.xaml | 2 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 45 +++--- src/AasxPackageExplorer/debug.MIHO.script | 4 +- .../options-debug.MIHO.json | 3 +- .../AdminShellPackageDynamicFetchEnv.cs | 2 +- .../PackageContainerHttpRepoSubset.cs | 141 ++++++++++++++++-- .../PackageCentral/PackageHttpDownloadUtil.cs | 55 +++++-- src/AasxPackageLogic/VisualAasxElements.cs | 26 ++++ 9 files changed, 236 insertions(+), 44 deletions(-) diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index dcae2210b..fecf0f309 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -539,7 +539,7 @@ public static string FilterFriendlyName(string src, if (pascalCase && src.Length > 0) src = char.ToUpper(src[0]) + src.Substring(1); - var regex = regexForFilter ?? @"[^a-zA-Z0-9\-_]"; + var regex = regexForFilter ?? @"[^a-zA-Z0-9_]"; src = Regex.Replace(src, regex, "_"); if (fixMoreBlanks) diff --git a/src/AasxPackageExplorer/MainWindow.xaml b/src/AasxPackageExplorer/MainWindow.xaml index 87a574165..81652d73c 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml +++ b/src/AasxPackageExplorer/MainWindow.xaml @@ -424,7 +424,7 @@