diff --git a/src/AasxCsharpLibrary/AdminShellUtil.cs b/src/AasxCsharpLibrary/AdminShellUtil.cs index bad07747b..6a22a2c3e 100644 --- a/src/AasxCsharpLibrary/AdminShellUtil.cs +++ b/src/AasxCsharpLibrary/AdminShellUtil.cs @@ -1458,11 +1458,21 @@ public static bool CheckIfUriIsAttachment(string uri) if (uri?.HasContent() != true || uri.Length < 2) return false; - // first char needs to be a slash, second must not be a slash!! - // Note: URIs starting with double slashes are called protocol-relative URLs or scheme-relative URIs + // can extract scheme and path -> no attachment + var p = uri.IndexOf("://"); + if (p >= 0 && p <= 5) + return false; + // old style supplemental files: first char needs to be a slash, second must not be a slash!! + // Note: URIs starting with double slashes are called protocol-relative URLs or scheme-relative URIs if (uri[0] == '/' && uri[2] != '/') return true; + + // Basyx style for attachment: starting with a 'normal' char + if (char.IsAsciiLetterOrDigit(uri[0])) + return true; + + // rest of the cases: assume is external return false; } diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index bd37a9999..4e1a154ea 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -1218,7 +1218,7 @@ public static List RenameIdentifiable(this AasCore.Aas3_0.IEnviro { // directly replace r.Keys[i].Value = newId; - if (res.Contains(lr.Identifiable)) + if (!res.Contains(lr.Identifiable)) res.Add(lr.Identifiable); } } diff --git a/src/AasxCsharpLibrary/Extensions/ExtendILangStringNameType.cs b/src/AasxCsharpLibrary/Extensions/ExtendILangStringNameType.cs index 7aef2526e..fd6d03947 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendILangStringNameType.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendILangStringNameType.cs @@ -62,5 +62,15 @@ public static bool IsValid(this List elems) return false; return true; } + + public static bool Contains(this List elems, String value, StringComparison comparisonType) + { + if (elems == null || elems.Count < 1) + return false; + var res = false; + foreach (var ls in elems) + res = res || ls?.Text?.Contains(value, comparisonType) == true; + return res; + } } } diff --git a/src/AasxCsharpLibrary/Extensions/ExtendILangStringTextType.cs b/src/AasxCsharpLibrary/Extensions/ExtendILangStringTextType.cs index c2af90af6..17d8ac166 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendILangStringTextType.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendILangStringTextType.cs @@ -52,5 +52,15 @@ public static bool IsValid(this List elems) return false; return true; } + + public static bool Contains(this List elems, String value, StringComparison comparisonType) + { + if (elems == null || elems.Count < 1) + return false; + var res = false; + foreach (var ls in elems) + res = res || ls?.Text?.Contains(value, comparisonType) == true; + return res; + } } } diff --git a/src/AasxCsharpLibrary/Extensions/ExtendIReferable.cs b/src/AasxCsharpLibrary/Extensions/ExtendIReferable.cs index 726f63559..e81930050 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendIReferable.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendIReferable.cs @@ -465,7 +465,7 @@ public static Submodel GetParentSubmodel(this IReferable referable) return parent as Submodel; } - public static string CollectIdShortByParent( + public static string CollectIdShortPathByParent( this IReferable referable, char separatorChar = '/', bool excludeIdentifiable = false) @@ -476,7 +476,7 @@ public static string CollectIdShortByParent( && referable.Parent is IReferable parentReferable && (!excludeIdentifiable || parentReferable is not IIdentifiable)) // can go up - head = parentReferable.CollectIdShortByParent(separatorChar, excludeIdentifiable) + head = parentReferable.CollectIdShortPathByParent(separatorChar, excludeIdentifiable) + separatorChar; // add own var myid = ""; diff --git a/src/AasxCsharpLibrary/Extensions/ExtendISubmodelElement.cs b/src/AasxCsharpLibrary/Extensions/ExtendISubmodelElement.cs index f625e4716..0e3b29804 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendISubmodelElement.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendISubmodelElement.cs @@ -1115,6 +1115,34 @@ public static IEnumerable Join(params IEnumerable parents, + char separatorChar = '/', + bool excludeIdentifiable = false) + { + // access + if (sme == null) + return null; + var path = "" + sme.IdShort?.Trim(); + + // now put the parents in front + if (parents != null) + foreach (var parent in parents.Reverse()) + { + // exclude + if (parent == null || string.IsNullOrEmpty(parent.IdShort)) + continue; + if (excludeIdentifiable && parent is IIdentifiable) + continue; + // prepend + path = parent.IdShort.Trim() + separatorChar + path; + } + + // ok + return path; + } + public static void RecurseOnReferables( this List submodelElements, object state, List parents, Func, IReferable, bool> lambda) diff --git a/src/AasxCsharpLibrary/PackageEnv/AdminShellPackageEnvBase.cs b/src/AasxCsharpLibrary/PackageEnv/AdminShellPackageEnvBase.cs index 91d4f557f..e3c3b2ef0 100644 --- a/src/AasxCsharpLibrary/PackageEnv/AdminShellPackageEnvBase.cs +++ b/src/AasxCsharpLibrary/PackageEnv/AdminShellPackageEnvBase.cs @@ -676,7 +676,8 @@ public virtual async Task MakePackageFileAvailableAsTempFileAsync( string aasId = null, string smId = null, string idShortPath = null, - bool keepFilename = false) + bool keepFilename = false, + ISecurityAccessHandler secureAccess = null) { // this uses the virtual implementation and should therefore work ok in the base class @@ -686,7 +687,7 @@ public virtual async Task MakePackageFileAvailableAsTempFileAsync( // get input stream var inputBytes = await GetBytesFromPackageOrExternalAsync(packageUri, aasId, smId, - idShortPath: idShortPath); + idShortPath: idShortPath, secureAccess: secureAccess); if (inputBytes == null) return null; diff --git a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs index ea1878f7c..150220c50 100644 --- a/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs +++ b/src/AasxIntegrationBaseGdi/AnyUI/AnyUiMagickHelper.cs @@ -307,7 +307,7 @@ public void Add( if (package == null || aas == null || submodel == null || fileElem == null) return; - var idShortPath = "" + fileElem.CollectIdShortByParent( + var idShortPath = "" + fileElem.CollectIdShortPathByParent( separatorChar: '.', excludeIdentifiable: true); diff --git a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs index 916f8b156..35959ae16 100644 --- a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs +++ b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs @@ -85,11 +85,11 @@ public string DetermineInitialDirectory(string existingFn = null) /// /// Redraw tree elements (middle), AAS entitty (right side) /// - public void CommandExecution_RedrawAll() + public async Task CommandExecution_RedrawAllAsync() { // redraw everything - RedrawAllAasxElements(); - RedrawElementView(); + await RedrawAllAasxElementsAsync(); + await RedrawElementViewAsync(); } /// @@ -307,9 +307,9 @@ private async Task CommandBinding_GeneralDispatch( currMdo = DisplayElements.SelectedItem.GetMainDataObject(); // edit mode affects the total element view - RedrawAllAasxElements(); + await RedrawAllAasxElementsAsync(); // fake selection - RedrawElementView(); + await RedrawElementViewAsync(); // select last object if (currMdo != null) { @@ -468,7 +468,7 @@ public void PanelConcurrentSetVisibleIfRequired( } } - public void CommandBinding_CheckAndFix() + public async Task CommandBinding_CheckAndFix() { // work on package var msgBoxHeadline = "Check, validate and fix .."; @@ -561,7 +561,7 @@ public void CommandBinding_CheckAndFix() AnyUiMessageBoxButton.OK, AnyUiMessageBoxImage.Information); // redraw - CommandExecution_RedrawAll(); + await CommandExecution_RedrawAllAsync(); } } @@ -1274,7 +1274,7 @@ public bool MenuSelectOpenFilename( return true; } - public void CommandBinding_ImportDictToSubmodel( + public async Task CommandBinding_ImportDictToSubmodel( string cmd, AasxMenuActionTicket ticket = null) { @@ -1326,7 +1326,7 @@ public void CommandBinding_ImportDictToSubmodel( if (dataChanged) { Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait; - RestartUIafterNewPackage(); + await RestartUIafterNewPackage(); Mouse.OverrideCursor = null; } #endif @@ -1362,7 +1362,7 @@ public void CommandBinding_ImportDictToSubmodel( if (dataChanged) { Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait; - RestartUIafterNewPackage(); + await RestartUIafterNewPackage(); Mouse.OverrideCursor = null; } #endif @@ -1398,141 +1398,8 @@ public async Task CommandBinding_ExportImportTableUml( $"Import/Export: While displaying html-based help."); } }; - // dead-csharp off - //if (cmd == "exporttable" || cmd == "importtable") - //{ - // if (ticket?.ScriptMode != true) - // { - // // interactive - // // handle the export dialogue - // var uc = new ExportTableFlyout((cmd == "exporttable") - // ? "Export SubmodelElements as Table" - // : "Import SubmodelElements from Table"); - // uc.Presets = Logic?.GetImportExportTablePreset().Item1; - - // StartFlyoverModal(uc); - - // if (uc.CloseForHelp) - // { - // callHelp?.Invoke(); - // return; - // } - - // if (uc.Result == null) - // return; - - // // have a result - // var record = uc.Result; - - // // be a little bit specific - // var dlgTitle = "Select text file to be exported"; - // var dlgFileName = ""; - // var dlgFilter = ""; - - // if (record.Format == (int)ImportExportTableRecord.FormatEnum.TSF) - // { - // dlgFileName = "new.txt"; - // dlgFilter = - // "Tab separated file (*.txt)|*.txt|Tab separated file (*.tsf)|*.tsf|All files (*.*)|*.*"; - // } - // if (record.Format == (int)ImportExportTableRecord.FormatEnum.LaTex) - // { - // dlgFileName = "new.tex"; - // dlgFilter = "LaTex file (*.tex)|*.tex|All files (*.*)|*.*"; - // } - // if (record.Format == (int)ImportExportTableRecord.FormatEnum.Excel) - // { - // dlgFileName = "new.xlsx"; - // dlgFilter = "Microsoft Excel (*.xlsx)|*.xlsx|All files (*.*)|*.*"; - // } - // if (record.Format == (int)ImportExportTableRecord.FormatEnum.Word) - // { - // dlgFileName = "new.docx"; - // dlgFilter = "Microsoft Word (*.docx)|*.docx|All files (*.*)|*.*"; - // } - // if (record.Format == (int)ImportExportTableRecord.FormatEnum.NarkdownGH) - // { - // dlgFileName = "new.md"; - // dlgFilter = "Markdown (*.md)|*.md|All files (*.*)|*.*"; - // } - - // // store - // ticket["Record"] = record; - - // // ask now for a filename - // if (!(await DisplayContext.MenuSelectSaveFilenameToTicketAsync( - // ticket, "File", - // dlgTitle, - // dlgFileName, - // dlgFilter, - // "Import/ export table: No valid filename."))) - // return; - // } - - // // pass on - // try - // { - // Logic?.CommandBinding_GeneralDispatchHeadless(cmd, null, ticket); - // } - // catch (Exception ex) - // { - // Logic?.LogErrorToTicket(ticket, ex, "Import/export table: passing on."); - // } - //} - //if (cmd == "importtimeseries") - //{ - // if (ticket?.ScriptMode != true) - // { - // // interactive - // // handle the export dialogue - // var uc = new ImportTimeSeriesFlyout(); - // uc.Result = Logic?.GetImportExportTablePreset().Item3 ?? new ImportTimeSeriesRecord(); - - // StartFlyoverModal(uc); - - // if (uc.Result == null) - // return; - - // // have a result - // var result = uc.Result; - - // // store - // ticket["Record"] = result; - - // // be a little bit specific - // var dlgTitle = "Select file for time series import .."; - // var dlgFilter = "All files (*.*)|*.*"; - - // if (result.Format == (int)ImportTimeSeriesRecord.FormatEnum.Excel) - // { - // dlgFilter = - // "Tab separated file (*.txt)|*.txt|Tab separated file (*.tsf)|*.tsf|All files (*.*)|*.*"; - // } - - // // ask now for a filename - // if (!(await DisplayContext.MenuSelectOpenFilenameToTicketAsync( - // ticket, "File", - // dlgTitle, - // null, - // dlgFilter, - // "Import time series: No valid filename."))) - // return; - // } - - // // pass on - // try - // { - // Logic?.CommandBinding_GeneralDispatchHeadless(cmd, null, ticket); - // } - // catch (Exception ex) - // { - // Logic?.LogErrorToTicket(ticket, ex, "Import time series: passing on."); - // } - //} - // dead-csharp on - // redraw - CommandExecution_RedrawAll(); + await CommandExecution_RedrawAllAsync(); } public async Task CommandBinding_ToolsFind( diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 5aa9b87af..36272df42 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -194,10 +194,12 @@ public void ClearAllViews() /// Try remember which element was focussed and focus it after redrawing. /// Focus a new main data object attached to an tree element. /// If focussing, expand this item. - public void RedrawAllAasxElements(bool keepFocus = false, + public async Task RedrawAllAasxElementsAsync(bool keepFocus = false, object nextFocusMdo = null, bool wishExpanded = true) { + await Task.Yield(); + // focus info var focusMdo = DisplayElements.SelectedItem?.GetDereferencedMainDataObject(); @@ -257,12 +259,12 @@ public bool CheckIsAnyTaintedIdentifiableInMain() /// Large extend. Basially redraws everything after new package has been loaded. /// /// Only tghe AUX package has been altered. - public void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null) + public async Task RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null) { if (onlyAuxiliary) { // reduced, in the background - RedrawAllAasxElements(); + await RedrawAllAasxElementsAsync(); } else { @@ -271,8 +273,8 @@ public void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditM // and -> this will update the left side of the screen correctly! MainMenu?.SetChecked("EditMenu", nextEditMode.HasValue ? nextEditMode.Value : false); ClearAllViews(); - RedrawAllAasxElements(); - RedrawElementView(); + await RedrawAllAasxElementsAsync(); + await RedrawElementViewAsync(); ShowContentBrowser(Options.Curr.ContentHome, silent: true); _eventHandling.Reset(); } @@ -382,7 +384,64 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() }, AllowFakeResponses = Options.Curr.AllowFakeResponses, ExtendedConnectionDebug = Options.Curr.ExtendedConnectionDebug, - SecurityAccessHandler = _securityAccessHandler + SecurityAccessHandler = _securityAccessHandler, + GetBaseUriForNewIdentifiablesHandler = async (defBaseUri, idf) => + { + // allready remembered + string baseUriStr = null; + if (Logic.RememberNewIdentifiableBaseUri + && Logic.RememberedNewIdentifiableBaseUriStr?.HasContent() == true) + { + baseUriStr = Logic.RememberedNewIdentifiableBaseUriStr; + } + else + { + // ask user + + var rec = new PackageContainerHttpRepoSubset.GetBaseAddressUploadRecord() + { + DisplayIdf = idf, + BaseAddress = defBaseUri.AbsoluteUri, + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + Remember = false, + }; + + var res = await PackageContainerHttpRepoSubset.PerformGetBaseAddressUploadDialogue( + ticket: null, + displayContext: DisplayContext, + caption: "Get Base Address for New Identifiable", + record: rec); + + if (res) + { + baseUriStr = rec.BaseAddress; + if (rec.Remember) + { + Logic.RememberNewIdentifiableBaseUri = true; + Logic.RememberedNewIdentifiableBaseUriStr = baseUriStr; + } + } + } + + // try convert to URI, again + if (baseUriStr == null) + return null; + try + { + var uris = new BaseUriDict(baseUriStr); + if (idf is Aas.IAssetAdministrationShell) + return uris.GetBaseUriForAasRepo(); + if (idf is Aas.ISubmodel) + return uris.GetBaseUriForSmRepo(); + if (idf is Aas.IConceptDescription) + return uris.GetBaseUriForCdRepo(); + return new Uri(baseUriStr); + } catch (Exception ex) + { + Log.Singleton.Error(ex, $"when building URi for new Identifiables from base address: {baseUriStr}"); + return null; + } + } }; return ro; } @@ -402,7 +461,7 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() /// 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( + public async Task UiLoadPackageWithNew( PackageCentralItem packItem, AdminShellPackageEnvBase takeOverEnv = null, string loadLocalFilename = null, @@ -467,7 +526,7 @@ public void UiLoadPackageWithNew( // displaying try { - RestartUIafterNewPackage(onlyAuxiliary, nextEditMode); + await RestartUIafterNewPackage(onlyAuxiliary, nextEditMode); if (autoFocusFirstRelevant && PackageCentral.Main?.AasEnv is Aas.IEnvironment menv) { @@ -519,7 +578,7 @@ public void UiLoadPackageWithNew( { // TODO (MIHO, 2020-12-31): check for ANYUI MIHO if (!doNotNavigateAfterLoaded) - Logic?.UiCheckIfActivateLoadedNavTo(); + await Logic?.UiCheckIfActivateLoadedNavTo(); TriggerPendingReIndexElements(); @@ -655,7 +714,7 @@ public void UiShowRepositories(bool visible) } } - public void PrepareDispEditEntity( + public async Task PrepareDispEditEntity( AdminShellPackageEnvBase package, ListOfVisualElementBasic entities, bool editMode, bool hintMode, bool showIriMode, bool checkSmt, DispEditHighlight.HighlightFieldInfo hightlightField = null) @@ -666,7 +725,7 @@ public void PrepareDispEditEntity( // update element view? DynamicMenu.Menu.Clear(); - var renderHints = DispEditEntityPanel.DisplayOrEditVisualAasxElement( + var renderHints = await DispEditEntityPanel.DisplayOrEditVisualAasxElement( PackageCentral, DisplayContext, entities, editMode, hintMode, showIriMode, checkSmt, tiCds?.CdSortOrder, flyoutProvider: this, @@ -716,7 +775,7 @@ public void PrepareDispEditEntity( // show it if (ElementTabControl.SelectedIndex != 0) - Dispatcher.BeginInvoke((Action)(() => ElementTabControl.SelectedIndex = 0)); + await Dispatcher.BeginInvoke((Action)(() => ElementTabControl.SelectedIndex = 0)); // some entities require special handling if (entities?.ExactlyOne == true && entities.First() is VisualElementSubmodelElement sme) @@ -742,8 +801,10 @@ public void PrepareDispEditEntity( /// Based on save information, will redraw the AAS entity (element) view (right). /// /// Highlight field (for find/ replace) - public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightField = null) + public async Task RedrawElementViewAsync(DispEditHighlight.HighlightFieldInfo hightlightField = null) { + await Task.Yield(); + if (DisplayElements == null) return; @@ -901,7 +962,7 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie } // for all, prepare the display - PrepareDispEditEntity( + await PrepareDispEditEntity( PackageCentral.Main, DisplayElements.SelectedItems, MainMenu?.IsChecked("EditMenu") == true, @@ -1300,7 +1361,7 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) // start with a new file PackageCentral.MainItem.New(); - RedrawAllAasxElements(); + await RedrawAllAasxElementsAsync(); // pump all pending log messages (from plugins) into the // log / status line, before setting the last information @@ -1566,7 +1627,7 @@ private async Task MainTimer_HandleLambdaAction(AnyUiLambdaActionBase lab) // edit mode affects the total element view if (!wish.OnlyReFocus) - RedrawAllAasxElements(); + await RedrawAllAasxElementsAsync(); // the selection will be shifted .. if (wish.NextFocus != null && DisplayElements != null) @@ -1586,7 +1647,7 @@ private async Task MainTimer_HandleLambdaAction(AnyUiLambdaActionBase lab) DispEditHighlight.HighlightFieldInfo hfi = null; if (lab is AnyUiLambdaActionRedrawAllElements wishhl) hfi = wishhl.HighlightField; - RedrawElementView(hightlightField: hfi); + await RedrawElementViewAsync(hightlightField: hfi); // ok DisplayElements.Refresh(); @@ -1614,6 +1675,7 @@ private async Task MainTimer_HandleLambdaAction(AnyUiLambdaActionBase lab) var rf = tempNavTo.targetReference.Copy(); if (tempNavTo.translateAssetToAAS + && rf?.IsValid() == true && rf.Keys.Count == 1 && rf.Keys.First().Type == Aas.KeyTypes.GlobalReference) //TODO (jtikekar, 0000-00-00): KeyType.AssetInformation @@ -1634,7 +1696,7 @@ private async Task MainTimer_HandleLambdaAction(AnyUiLambdaActionBase lab) } } - // handle it by UI + // handle it by UI (may include repo lookup) await UiHandleNavigateTo(rf, alsoDereferenceObjects: tempNavTo.alsoDereferenceObjects); } @@ -1900,8 +1962,10 @@ private void UiHandleReRenderAnyUiInEntityPanel( // search for AAS? BaseUriDict baseUris = null; + var searches = new List>(); if (workRef?.IsValid() == true) { + // search for AAS? if (workRef.Count() >= 1 && workRef.Keys[0].Type == KeyTypes.AssetAdministrationShell) { // want to search for an AAS @@ -1910,6 +1974,8 @@ private void UiHandleReRenderAnyUiInEntityPanel( var basedLoc = PackageContainerHttpRepoSubset.BuildLocationFrom(record); baseUris = basedLoc.BaseUris; fullItemLocation = basedLoc.Location.ToString(); + searches.Add( + new Tuple(record, baseUris, fullItemLocation)); } // search for Asset? @@ -1921,33 +1987,73 @@ private void UiHandleReRenderAnyUiInEntityPanel( var basedLoc = PackageContainerHttpRepoSubset.BuildLocationFrom(record); baseUris = basedLoc.BaseUris; fullItemLocation = basedLoc.Location.ToString(); + searches.Add( + new Tuple(record, baseUris, fullItemLocation)); + } + + // search for Submodel? + if (workRef.Count() >= 1 && (workRef.Keys[0].Type == KeyTypes.GlobalReference + || workRef.Keys[0].Type == KeyTypes.Submodel)) + { + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleSM); + record.SmId = workRef.Keys[0].Value; + var basedLoc = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + baseUris = basedLoc.BaseUris; + fullItemLocation = basedLoc.Location.ToString(); + searches.Add( + new Tuple(record, baseUris, fullItemLocation)); + } + + // search for CD? + if (workRef.Count() >= 1 && (workRef.Keys[0].Type == KeyTypes.GlobalReference + || workRef.Keys[0].Type == KeyTypes.ConceptDescription)) + { + // want to search for an CD? + record.SetQueryChoices(ConnectExtendedRecord.QueryChoice.SingleCD); + record.CdId = workRef.Keys[0].Value; + var basedLoc = PackageContainerHttpRepoSubset.BuildLocationFrom(record); + baseUris = basedLoc.BaseUris; + fullItemLocation = basedLoc.Location.ToString(); + searches.Add( + new Tuple(record, baseUris, fullItemLocation)); } } - // any info - if (fullItemLocation?.HasContent() != true) + // any searches? + if (searches.Count < 1) return null; - // try to load + // try to load in sequence, until new Identifiable is found // TODO: take over those options from existing container - var containerOptions = new PackageContainerHttpRepoSubset. - PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), - record); - containerOptions.BaseUris = baseUris; - - var newIdfs = new List(); - var loadedIdfs = new List(); - - var loadRes = await PackageContainerHttpRepoSubset.LoadFromSourceToTargetAsync( - fullItemLocation: fullItemLocation, - targetEnv: packEnv, - loadNew: false, - trackNewIdentifiables: newIdfs, - trackLoadedIdentifiables: loadedIdfs, - containerOptions: containerOptions, - runtimeOptions: PackageCentral.CentralRuntimeOptions); - - if (loadRes == null || newIdfs.Count < 1) + var foundIdfs = new List(); + foreach (var search in searches) + { + var containerOptions = new PackageContainerHttpRepoSubset. + PackageContainerHttpRepoSubsetOptions(PackageContainerOptionsBase.CreateDefault(Options.Curr), + search.Item1); + containerOptions.BaseUris = search.Item2; + + var newIdfs = new List(); + var loadedIdfs = new List(); + + var loadRes = await PackageContainerHttpRepoSubset.LoadFromSourceToTargetAsync( + fullItemLocation: search.Item3, + targetEnv: packEnv, + loadNew: false, + trackNewIdentifiables: newIdfs, + trackLoadedIdentifiables: loadedIdfs, + containerOptions: containerOptions, + runtimeOptions: PackageCentral.CentralRuntimeOptions); + + if (loadRes != null && newIdfs.Count >= 1) + { + foundIdfs.AddRange(newIdfs); + // may be in the future, we want also NOT to break here? + break; + } + } + + if (foundIdfs.Count < 1) return null; // rebuild display elements @@ -1955,7 +2061,7 @@ private void UiHandleReRenderAnyUiInEntityPanel( PackageCentral, PackageCentral.Selector.Main, MainMenu?.IsChecked("EditMenu") == true, lazyLoadingFirst: true); - var newIdf = newIdfs.FirstOrDefault(); + var newIdf = foundIdfs.FirstOrDefault(); // display if (trySelect) @@ -1964,11 +2070,12 @@ private void UiHandleReRenderAnyUiInEntityPanel( if (veFound != null) { // show ve + DisplayElements.ExpandAllItems(); DisplayElements.TrySelectVisualElement(veFound, wishExpanded: true); // remember in history Logic?.LocationHistory?.Push(veFound); // fake selection - RedrawElementView(); + await RedrawElementViewAsync(); DisplayElements.Refresh(); TakeOverContentEnable(false); } @@ -2042,6 +2149,7 @@ private async Task UiHandleNavigateTo( // try to look up in visual elements if (this.DisplayElements != null) { + DisplayElements.ExpandAllItems(); var ve = this.DisplayElements.SearchVisualElementOnMainDataObject(bo, alsoDereferenceObjects: alsoDereferenceObjects, sri: sri); if (ve != null) @@ -2071,7 +2179,7 @@ private async Task UiHandleNavigateTo( // remember in history Logic?.LocationHistory?.Push(veFound); // fake selection - RedrawElementView(); + await RedrawElementViewAsync(); DisplayElements.Refresh(); TakeOverContentEnable(false); } @@ -2820,7 +2928,7 @@ private void MainTimer_PeriodicalTaskForSelectedEntity() } } - public void MainTaimer_HandleIncomingAasEvents() + public async Task MainTaimer_HandleIncomingAasEvents() { int nEvent = 0; while (true) @@ -2879,7 +2987,7 @@ public void MainTaimer_HandleIncomingAasEvents() // Note: do not re-display plugins!! var ves = DisplayElements.SelectedItem; if (ves != null && (ves is VisualElementSubmodelRef || ves is VisualElementSubmodelElement)) - RedrawElementView(); + await RedrawElementViewAsync(); } } } @@ -3017,7 +3125,7 @@ private async Task ButtonHistory_ObjectRequested(object sender, VisualElementHis if (DisplayElements.TrySelectVisualElement(ve, wishExpanded: true)) { // fake selection - RedrawElementView(); + await RedrawElementViewAsync(); DisplayElements.Refresh(); TakeOverContentEnable(false); @@ -3033,7 +3141,7 @@ private async Task ButtonHistory_ObjectRequested(object sender, VisualElementHis if (DisplayElements.TrySelectMainDataObject(bo, wishExpanded: true, alsoDereferenceObjects: true)) { // fake selection - RedrawElementView(); + await RedrawElementViewAsync(); DisplayElements.Refresh(); TakeOverContentEnable(false); @@ -3094,7 +3202,7 @@ private async Task ButtonHistory_ObjectRequested(object sender, VisualElementHis //TODO (MIHO, 0000-00-00): this was a bug?? // ButtonHistory.Push(veFocus); // fake selection - RedrawElementView(); + await RedrawElementViewAsync(); DisplayElements.Refresh(); TakeOverContentEnable(false); } @@ -3229,7 +3337,7 @@ public void SaveScreenshot(string filename = "noname") })); } - private void DisplayElements_SelectedItemChanged(object sender, EventArgs e) + private async void DisplayElements_SelectedItemChanged(object sender, EventArgs e) { // access if (DisplayElements == null || sender != DisplayElements) @@ -3245,7 +3353,7 @@ private void DisplayElements_SelectedItemChanged(object sender, EventArgs e) CheckIfToFlushEvents(); // redraw view - RedrawElementView(); + await RedrawElementViewAsync(); } private async void DisplayElements_MouseDoubleClick(object sender, MouseButtonEventArgs e) @@ -3300,7 +3408,7 @@ private async void DisplayElements_MouseDoubleClick(object sender, MouseButtonEv if (si is VisualElementSubmodelElement) { // redraw view - RedrawElementView(); + await RedrawElementViewAsync(); // "simulate" click on "ShowContents" this.ShowContent_Click(this.ShowContent, null); @@ -3425,7 +3533,8 @@ private async void ShowContent_Click(object sender, RoutedEventArgs e) if (this.DisplayContext.StartFlyoverModal(uc)) { blb.Value = Encoding.Default.GetBytes(uc.Text); - RedrawElementView(); + DispEditEntityPanel.AddDiaryStructuralChange(blb); + await RedrawElementViewAsync(); } } catch (Exception ex) @@ -3480,7 +3589,8 @@ private async void ShowContent_Click(object sender, RoutedEventArgs e) contentUri = await PackageCentral.Main.MakePackageFileAvailableAsTempFileAsync(contentUri, aasId: x?.Item1?.Id, smId: x?.Item2?.Id, - idShortPath: x?.Item3); + idShortPath: x?.Item3, + secureAccess: _securityAccessHandler); } BrowserDisplayLocalFile(contentUri, contentFound.Item2); @@ -3571,7 +3681,7 @@ public void CheckIfToFlushEvents() } } - private void ContentTakeOver_Click(object sender, RoutedEventArgs e) + private async void ContentTakeOver_Click(object sender, RoutedEventArgs e) { // some more "OK, good to go" CheckIfToFlushEvents(); @@ -3591,7 +3701,7 @@ private void ContentTakeOver_Click(object sender, RoutedEventArgs e) // (MIHO, 2024-07-02): redisplay (full re-render) only, if not currently // a plugin is display, which might have internal state! if (!(DisplayElements?.SelectedItem is VisualElementPluginExtension)) - RedrawElementView(); + await RedrawElementViewAsync(); // re-enable TakeOverContentEnable(false); diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index f0cbdeee0..45645dc4f 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -5,6 +5,7 @@ "NoHints": true /* This file shall be loaded as main package at start of application | Arg: */, "AasxToLoad": "http://localhost:8081/shells/aHR0cHM6Ly9hZG1pbi1zaGVsbC1pby9pZHRhL2Fhcy9Qcm9kdWN0Q2hhbmdlTm90aWZpY2F0aW9ucy8xLzA" + // "AasxToLoad": "C:\\Users\\Micha\\Desktop\\test-smt-on-basyx\\IDTA_02036-1-0_SMT_ProductChangeNotification_Draft_v35_manual_fixes.aasx" /* This file shall be loaded as aux package at start of application | Arg: */, "AuxToLoad": null /* List of pathes to a JSON, defining a set of AasxPackage-Files, which serve as repository. | Arg: */, @@ -73,6 +74,8 @@ "MaxParallelReadOps": 10 /* Maximum parallel write operations, such as HTTP uploads. */, "MaxParallelWriteOps": 1 + /* For connecting to repositories/ registry, default pagination limit. */, + "DefaultConnectPageLimit" : 20 /* If not -1, the left of window | Arg: */, "WindowLeft": -1 /* If not -1, the top of window | Arg: */, @@ -279,7 +282,7 @@ "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(\"ExportSmtAsciiDoc\", \"File\", \"C:\\Users\\Micha\\Desktop\\tmp\\new.zip\", \"ExportHtml\", \"true\", \"ExportPdf\", \"false\", \"AntoraStyle\", \"false\", \"ViewResult\", \"true\");", "Tool(\"LocationPop\");" ], "Text": "Tool(\"LocationPush\");\nSelect(\"Submodel\", \"First\");\nSelect(\"Submodel\", \"Next\");\nTool(\"ExportSmtAsciiDoc\", \"File\", \"C:\\Users\\homi0002\\Desktop\\tmp\\new.zip\", \"ExportHtml\", \"true\", \"ExportPdf\", \"false\", \"AntoraStyle\", \"false\", \"ViewResult\", \"true\");\nTool(\"LocationPop\");" diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index dffaeaab4..087a0bdab 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -312,7 +312,9 @@ public void AddKeyValueExRef( AnyUiStackPanel view, string key, object containingObject, string value, string nullValue = null, ModifyRepo repo = null, Func setValue = null, string[] comboBoxItems = null, bool comboBoxIsEditable = false, - string auxButtonTitle = null, Func auxButtonLambda = null, + string auxButtonTitle = null, + Func auxButtonLambda = null, + Func> auxButtonLambdaAsync = null, string auxButtonToolTip = null, string[] auxButtonTitles = null, string[] auxButtonToolTips = null, @@ -321,18 +323,20 @@ public void AddKeyValueExRef( int comboBoxMinWidth = -1, int firstColumnWidth = -1, // -1 = Standard int maxLines = -1, - bool keyVertCenter = false) + bool keyVertCenter = false, + bool auxButtonOverride = false) { AddKeyValue( view, key, value, nullValue, repo, setValue, comboBoxItems, comboBoxIsEditable, - auxButtonTitle, auxButtonLambda, auxButtonToolTip, + auxButtonTitle, auxButtonLambda, auxButtonLambdaAsync, auxButtonToolTip, auxButtonTitles, auxButtonToolTips, takeOverLambdaAction, (value == null) ? 0 : value.GetHashCode(), containingObject: containingObject, limitToOneRowForNoEdit: limitToOneRowForNoEdit, comboBoxMinWidth: comboBoxMinWidth, - firstColumnWidth: firstColumnWidth, + firstColumnWidth: firstColumnWidth, maxLines: maxLines, - keyVertCenter: keyVertCenter); + keyVertCenter: keyVertCenter, + auxButtonOverride: auxButtonOverride); } /// @@ -362,6 +366,7 @@ public void AddKeyValue( ModifyRepo repo = null, Func setValue = null, string[] comboBoxItems = null, bool comboBoxIsEditable = false, string auxButtonTitle = null, Func auxButtonLambda = null, + Func> auxButtonLambdaAsync = null, string auxButtonToolTip = null, string[] auxButtonTitles = null, string[] auxButtonToolTips = null, AnyUiLambdaActionBase takeOverLambdaAction = null, @@ -371,7 +376,7 @@ public void AddKeyValue( int comboBoxMinWidth = -1, int firstColumnWidth = -1, // -1 = Standard int maxLines = -1, - bool keyVertCenter = false, + bool keyVertCenter = true, bool auxButtonOverride = false) { // draw anyway? @@ -402,7 +407,7 @@ public void AddKeyValue( intButtonToolTips.AddRange(auxButtonToolTips); var auxButton = auxButtonOverride - || (repo != null && intButtonTitles.Count > 0 && auxButtonLambda != null); + || (repo != null && intButtonTitles.Count > 0 && (auxButtonLambda != null || auxButtonLambdaAsync != null)); // Grid var g = new AnyUiGrid(); @@ -442,7 +447,8 @@ public void AddKeyValue( { if (limitToOneRowForNoEdit) value = AdminShellUtil.RemoveNewLinesAndLimit("" + value, 120, ellipsis: "\u2026"); - AddSmallLabelTo(g, 0, 1, padding: new AnyUiThickness(4, 0, 0, 0), content: "" + value); + AddSmallLabelTo(g, 0, 1, padding: new AnyUiThickness(4, 0, 0, 0), content: "" + value, + verticalCenter: true); } else if (comboBoxItems != null) { @@ -497,19 +503,28 @@ public void AddKeyValue( for (int i = 0; i < intButtonTitles.Count; i++) { Func lmb = null; + Func> lmbAsync = null; int closureI = i; + if (auxButtonLambda != null) lmb = (o) => { return auxButtonLambda(closureI); // exchange o with i !! }; + + if (auxButtonLambdaAsync != null) + lmbAsync = async (o) => + { + return await auxButtonLambdaAsync(closureI); // exchange o with i !! + }; + var b = AnyUiUIElement.RegisterControl( AddSmallButtonTo( g, 0, 2 + i, margin: new AnyUiThickness(2, 2, 2, 2), padding: new AnyUiThickness(5, 0, 5, 0), content: intButtonTitles[i]), - lmb) as AnyUiButton; + lmb, lmbAsync) as AnyUiButton; if (i < intButtonToolTips.Count) b.ToolTip = intButtonToolTips[i]; } @@ -2540,10 +2555,11 @@ public void EntityListUpDownDeleteHelper( object nextFocus = null, PackCntChangeEventData sendUpdateEvent = null, bool preventMove = false, Aas.IReferable explicitParent = null, AasxMenu superMenu = null, - Action postActionHook = null, + Func postActionHookAsync = null, AasxMenu extraMenu = null, Func lambdaExtraMenu = null, - Func> lambdaExtraMenuAsync = null) + Func> lambdaExtraMenuAsync = null, + bool moveDoesNotModify = false) { if (nextFocus == null) nextFocus = entity; @@ -2600,9 +2616,10 @@ public void EntityListUpDownDeleteHelper( if (buttonNdx == 3) newndx = MoveElementToBottomOfList(list, entity); if (newndx >= 0) { - postActionHook?.Invoke(theMenu?.ElementAt(buttonNdx)?.Name, ticket); + if (postActionHookAsync != null) + await postActionHookAsync.Invoke(theMenu?.ElementAt(buttonNdx)?.Name, ticket); - if (entityRf != null) + if (entityRf != null && !moveDoesNotModify) this.AddDiaryEntry(entityRf, new DiaryEntryStructChange(StructuralChangeReason.Modify, createAtIndex: newndx), explicitParent: explicitParent); @@ -2634,7 +2651,7 @@ public void EntityListUpDownDeleteHelper( if (list.Count < 1) setOutputList?.Invoke(null); - postActionHook?.Invoke(theMenu?.ElementAt(buttonNdx)?.Name, ticket); + await postActionHookAsync?.Invoke(theMenu?.ElementAt(buttonNdx)?.Name, ticket); this.AddDiaryEntry(entityRf, new DiaryEntryStructChange(StructuralChangeReason.Delete), diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index a3eb127e6..584df5a1b 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -7,31 +7,32 @@ 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 AasxIntegrationBase.AdminShellEvents; -using AasxPackageLogic.PackageCentral; -using AdminShellNS; -using AnyUi; -using Extensions; using System; -using System.IO; using System.Collections.Generic; using System.Data; +using System.IO; using System.Linq; using System.Runtime.Intrinsics.X86; using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; using System.Windows.Documents; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; -using Aas = AasCore.Aas3_0; using AasCore.Samm2_2_0; -using static AasxPackageLogic.DispEditHelperBasics; -using System.Windows.Controls; +using AasxIntegrationBase; +using AasxIntegrationBase.AdminShellEvents; using AasxPackageExplorer; -using System.Threading.Tasks; -using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; +using AasxPackageLogic.PackageCentral; +using AdminShellNS; +using AnyUi; +using Extensions; using VDS.Common.Filters; -using static Lucene.Net.Search.FieldCache; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; +using static AasxCompatibilityModels.AdminShellV10; +using static AasxPackageLogic.DispEditHelperBasics; +using static AasxPackageLogic.PackageCentral.PackageContainerHttpRepoSubset; using static AnyUi.AnyUiDialogueDataTextEditor; +using static Lucene.Net.Search.FieldCache; +using Aas = AasCore.Aas3_0; namespace AasxPackageLogic { @@ -1629,6 +1630,11 @@ public void DisplayOrEditAasEntityAas( if (aas == null) return; + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.AssetAdministrationShells, aas.GetReference()); + // Entities if (editMode) { @@ -1639,6 +1645,14 @@ public void DisplayOrEditAasEntityAas( // main group this.AddGroup(stack, "Editing of entities", this.levelColors.SubSection); + // the event template will help speed up visual updates of the tree + var evTemplate = new PackCntChangeEventData() + { + Container = packages?.GetAllContainer((cnr) => cnr?.Env?.AasEnv == env).FirstOrDefault(), + ThisElem = aas, + ParentElem = env + }; + // Up/ down/ del this.EntityListUpDownDeleteHelper( stack, repo, @@ -1646,50 +1660,91 @@ public void DisplayOrEditAasEntityAas( aas, env, "AAS:", 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.") .AddAction("finalize-aas", "Finalize AAS", "Check and auto-correct AAS to be uploaded into Repository."), - lambdaExtraMenuAsync: async (buttonNdx) => + moveDoesNotModify: true, + sendUpdateEvent: evTemplate, + postActionHookAsync: async (actionName, ticket) => { - if (buttonNdx == 0) - { - // check, if Submodel is sitting in Repo - var sideInfo = OnDemandListIdentifiable - .FindSideInfoInListOfIdentifiables( - env.AssetAdministrationShells, aas.GetReference()); + await Task.Yield(); + if (actionName == "aas-elem-delete") + { + // be a bit informy + Log.Singleton.Info($"Deleted AAS {aas.IdShort} from local Environment."); + // simply prepare some Keys! - var idfKeys = (new Aas.IKey[] { + 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, + // check if to delete Submodels in local Environment? + var delInLocal = AnyUiMessageBoxResult.Yes == await this.context.MessageBoxFlyoutShowAsync( + "Delete Submodels in local Environment as well? " + + "This operation can not be reverted!", + "Delete Submodels in local Environment?", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning); + if (delInLocal) + { + foreach (var ik in idfKeys) + if (ik.Type == KeyTypes.Submodel) + { + var sm = env?.FindSubmodelById(ik.Value); + if (sm != null) + { + env.Remove(sm); + Log.Singleton.Info($"Deleted Submodel {sm.IdShort} from local Environment."); + } + } + } - // extract base address - BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( - sideInfo?.DesignatedEndpoint?.AbsoluteUri)?.AbsoluteUri - }); + // delete in repo as well? + var delInRepo = sideInfo?.Id?.HasContent() == true + && sideInfo.StubLevel >= AasIdentifiableSideInfoLevel.IdOnly; - // ok - return new AnyUiLambdaActionNone(); - } + if (delInRepo) + { + if (AnyUiMessageBoxResult.Yes != await this.context.MessageBoxFlyoutShowAsync( + "Delete AAS in Repository as well? " + + "This operation can not be reverted!", + "Delete in Repository?", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + delInRepo = false; + } - if (buttonNdx == 1) + // delete in repo? + if (delInRepo) + { + // 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 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?.DesignatedEndpoint?.AbsoluteUri)?.AbsoluteUri + }); + } + + // manually redraw + this.appEventsProvider?.PushApplicationEvent(new AasxPluginResultEventRedrawAllElements()); + } + }, + lambdaExtraMenuAsync: async (buttonNdx) => + { + await Task.Yield(); + if (buttonNdx == 0) { // get a list var idfs = env?.FindAllReferencedIdentifiablesForAas(aas); @@ -2051,9 +2106,6 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( } // info about sideInfo - var sideInfo = OnDemandListIdentifiable - .FindSideInfoInListOfIdentifiables( - env.AssetAdministrationShells, aas.GetReference()); DisplayOrEditEntitySideInfo(env, stack, aas, sideInfo, "AAS", superMenu); // Referable @@ -2064,11 +2116,12 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( // Identifiable this.DisplayOrEditEntityIdentifiable( - stack, env, aas, + stack, package, env, aas, Options.Curr.TemplateIdAas, injectToId: new DispEditHelperModules.DispEditInjectAction( new[] { "Rename" }, - (i) => + auxLambda: null, + auxLambdaAsync: async (i) => { if (i == 0 && env != null) { @@ -2077,15 +2130,64 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( symbol: AnyUiMessageBoxImage.Question, maxWidth: 1400, text: aas.Id); - if (this.context.StartFlyoverModal(uc)) + if (await this.context.StartFlyoverModalAsync(uc) + && uc.Text?.HasContent() == true) { + var oldId = aas.Id; + var newId = uc.Text.Trim(); var res = false; try { + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.AssetAdministrationShells, aas.GetReference()); + if (sideInfo != null) + { + // in any case, update Id + sideInfo.Id = newId; + + // ask user for repo operation + if (AnyUiMessageBoxResult.Yes == await this.context.MessageBoxFlyoutShowAsync( + "Rename AAS in Repository as well? This operation can not be reverted!", + "Rename Identifiable", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + { + + // rename in repo + // (only the side info in the _specific_ endpoint gives information, in + // which repo the Indentifiables could be deleted) + var newEndpoint = await PackageContainerHttpRepoSubset + .AssistantRenameIdfsInRepo( + baseUri: PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo.DesignatedEndpoint?.AbsoluteUri), + oldId: oldId, + newId: newId, + runtimeOptions: packages.CentralRuntimeOptions, + moreLog: true); + + Log.Singleton.Info("Rename in repo performed successfully."); + + // adopt in side info + sideInfo.QueriedEndpoint = newEndpoint; + sideInfo.DesignatedEndpoint = newEndpoint; + } + } + // rename var lrf = env.RenameIdentifiable( - aas.Id, uc.Text); + oldId, newId); + + Log.Singleton.Info("Rename in ram-based environment performed successfully."); + + // rename in environment helper structures? + if (package is AdminShellPackageDynamicFetchEnv dynPack) + { + // rename in dynamic fetch environment + dynPack.RenameThumbnailData(oldId, newId); + Log.Singleton.Info("Rename of thumbnail in dynamic fetch environment performed successfully."); + } // use this information to emit events if (lrf != null) @@ -2262,11 +2364,20 @@ public void DisplayOrEditAasEntitySubmodelOrRef( jumpLambda: lambda, relatedReferable: aas); } + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.Submodels, submodel?.GetReference()); + // entities when under AAS (smref) if (editMode && smref != null) { this.AddGroup(stack, "Editing of entities (within specific AAS)", this.levelColors.SubSection); + // + // Up/Down Helper for SM References! + // + // the event template will help speed up visual updates of the tree var evTemplate = new PackCntChangeEventData() { @@ -2280,10 +2391,11 @@ public void DisplayOrEditAasEntitySubmodelOrRef( aas.Submodels, (lst) => { aas.Submodels = lst; }, smref, aas, "Reference:", sendUpdateEvent: evTemplate, explicitParent: aas, - postActionHook: (actionName, ticket) => { + postActionHookAsync: async (actionName, ticket) => { + await Task.Yield(); if (actionName == "aas-elem-delete") { - // ask + // ask for complete deletion if (ticket?.ScriptMode != true && AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( "Delete selected Submodel for all AAS in the Environment? " + @@ -2296,49 +2408,49 @@ public void DisplayOrEditAasEntitySubmodelOrRef( if (smExist != null) env.Remove(smExist); - // 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()); + // delete in repo as well? + var delInRepo = sideInfo?.Id?.HasContent() == true + && sideInfo.StubLevel >= AasIdentifiableSideInfoLevel.IdOnly; - // simply prepare one Key! - var smKey = new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, "" + submodel.Id) }; + if (delInRepo) + { + if (AnyUiMessageBoxResult.Yes != await this.context.MessageBoxFlyoutShowAsync( + "Delete Submodel in Repository as well? " + + "This operation can not be reverted!", + "Delete Identifiable", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + delInRepo = false; + } - // 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, + // delete in repo? + if (delInRepo) + { + // 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?.DesignatedEndpoint?.AbsoluteUri)?.AbsoluteUri - }); + // extract base address + BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo?.DesignatedEndpoint?.AbsoluteUri)?.AbsoluteUri + }); + } - // ok - return new AnyUiLambdaActionNone(); + // manually redraw + this.appEventsProvider?.PushApplicationEvent(new AasxPluginResultEventRedrawAllElements()); } - - return new AnyUiLambdaActionNone(); }); } @@ -2349,6 +2461,75 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( stack, "Editing of entities (within environment)", this.levelColors.MainSection); + // + // Up/Down Helper for Submodels themself! + // + + // the event template will help speed up visual updates of the tree + var evTemplate = new PackCntChangeEventData() + { + Container = packages?.GetAllContainer((cnr) => cnr?.Env?.AasEnv == env).FirstOrDefault(), + ThisElem = submodel, + ParentElem = env + }; + + this.EntityListUpDownDeleteHelper( + stack, repo, + env.Submodels, (lst) => { env.Submodels = lst; }, + submodel, alternativeFocus: env, + "Submodel:", sendUpdateEvent: evTemplate, + explicitParent: aas, + moveDoesNotModify: true, + postActionHookAsync: async (actionName, ticket) => { + await Task.Yield(); + if (actionName == "aas-elem-delete") + { + // delete in repo as well? + var delInRepo = sideInfo?.Id?.HasContent() == true + && sideInfo.StubLevel >= AasIdentifiableSideInfoLevel.IdOnly; + + if (delInRepo) + { + if (AnyUiMessageBoxResult.Yes != await this.context.MessageBoxFlyoutShowAsync( + "Delete Submodel in Repository as well? " + + "This operation can not be reverted!", + "Delete Identifiable", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + delInRepo = false; + } + + // delete in repo? + if (delInRepo) + { + // 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?.DesignatedEndpoint?.AbsoluteUri)?.AbsoluteUri + }); + } + + // manually redraw + this.appEventsProvider?.PushApplicationEvent(new AasxPluginResultEventRedrawAllElements()); + } + }); + +#if __old_not_required_anymore AddActionPanel(stack, "Submodel:", repo: repo, superMenu: superMenu, ticketMenu: new AasxMenu() @@ -2389,6 +2570,15 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( .FindSideInfoInListOfIdentifiables( env.Submodels, submodel.GetReference()); + // 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, "" + submodel.Id) }; @@ -2417,6 +2607,7 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( return new AnyUiLambdaActionNone(); }); +#endif } // Cut, copy, paste within an aas @@ -2641,6 +2832,12 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( .FindSideInfoInListOfIdentifiables( env.Submodels, submodel.GetReference()); + if (sideInfo == null) + { + Log.Singleton.Error("Not enough information to delete AAS in Repository/ Registry!"); + return new AnyUiLambdaActionNone(); + } + // collect Ids of SubmodelElements.semanticId var lrs = env?.FindAllSemanticIdsForSubmodel(submodel); if (lrs == null) @@ -3002,9 +3199,6 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( // info about sideInfo if (submodel != null) { - var sideInfo = OnDemandListIdentifiable - .FindSideInfoInListOfIdentifiables( - env.Submodels, submodel.GetReference()); DisplayOrEditEntitySideInfo(env, stack, submodel, sideInfo, "Submodel", superMenu); } else @@ -3031,13 +3225,14 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( // Identifiable this.DisplayOrEditEntityIdentifiable( - stack, env, submodel, + stack, packEnv, env, submodel, (submodel.Kind == Aas.ModellingKind.Template) ? Options.Curr.TemplateIdSubmodelTemplate : Options.Curr.TemplateIdSubmodelInstance, new DispEditHelperModules.DispEditInjectAction( new[] { "Rename" }, - (i) => + auxLambda: null, + auxLambdaAsync: async (i) => { if (i == 0 && env != null) { @@ -3048,10 +3243,47 @@ await PackageContainerHttpRepoSubset.AssistantDeleteIdfsInRepo( text: submodel.Id); if (this.context.StartFlyoverModal(uc)) { + var oldId = submodel.Id; + var newId = uc.Text.Trim(); var res = false; try { + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.Submodels, submodel.GetReference()); + if (sideInfo != null) + { + // in any case, update Id + sideInfo.Id = newId; + + // ask user for repo operation + if (AnyUiMessageBoxResult.Yes == await this.context.MessageBoxFlyoutShowAsync( + "Rename Submodel in Repository as well? This operation can not be reverted!", + "Rename Identifiable", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + { + // rename in repo + // (only the side info in the _specific_ endpoint gives information, in + // which repo the Indentifiables could be deleted) + var newEndpoint = await PackageContainerHttpRepoSubset + .AssistantRenameIdfsInRepo( + baseUri: PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo.DesignatedEndpoint?.AbsoluteUri), + oldId: oldId, + newId: newId, + runtimeOptions: packages.CentralRuntimeOptions, + moreLog: true); + + Log.Singleton.Info("Rename in repo performed successfully."); + + // adopt in side info + sideInfo.QueriedEndpoint = newEndpoint; + sideInfo.DesignatedEndpoint = newEndpoint; + } + } + // rename var lrf = env.RenameIdentifiable( submodel.Id, uc.Text); @@ -3292,6 +3524,11 @@ public void DisplayOrEditAasEntityConceptDescription( { this.AddGroup(stack, "ConceptDescription", this.levelColors.MainSection); + // info about sideInfo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.ConceptDescriptions, cd.GetCdReference()); + // Up/ down/ del if (editMode && !embedded) { @@ -3310,7 +3547,47 @@ public void DisplayOrEditAasEntityConceptDescription( env.ConceptDescriptions, (lst) => { env.ConceptDescriptions = lst; }, cd, env, "CD:", sendUpdateEvent: evTemplate, preventMove: preventMove, - superMenu: superMenu); + superMenu: superMenu, + moveDoesNotModify: true, + postActionHookAsync: async (actionName, ticket) => + { + await Task.Yield(); + + // Note: sideinfo needs to be looked up before the helper, as the helper might + // delete it! + if (sideInfo?.Id?.HasContent() != true || sideInfo.StubLevel < AasIdentifiableSideInfoLevel.IdOnly) + return; + + // ask? + if (AnyUiMessageBoxResult.Yes != await this.context.MessageBoxFlyoutShowAsync( + "Delete ConceptDescription in Repository as well? " + + "This operation can not be reverted!", + "Delete Identifiable", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + return; + + // simply prepare one Key! + var cdKey = new Aas.IKey[] { new Aas.Key(KeyTypes.ConceptDescription, "" + cd.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 ConceptDescription in Repository/ Registry", + "ConceptDescription", + cdKey, + runtimeOptions: packages.CentralRuntimeOptions, + presetRecord: new PackageContainerHttpRepoSubset.DeleteAssistantJobRecord() + { + // assume Repo ?! + BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository, + + // extract base address + BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo?.DesignatedEndpoint?.AbsoluteUri)?.AbsoluteUri + }); + }); } // Cut, copy, paste within list of CDs @@ -3323,11 +3600,6 @@ 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 @@ -3380,11 +3652,12 @@ public void DisplayOrEditAasEntityConceptDescription( Action lambdaIdf = () => { this.DisplayOrEditEntityIdentifiable( - stack, env, cd, + stack, packages?.Main, env, cd, Options.Curr.TemplateIdConceptDescription, new DispEditHelperModules.DispEditInjectAction( new[] { "Rename" }, - (i) => + auxLambda: null, + auxLambdaAsync: async (i) => { if (i == 0 && env != null) { @@ -3395,10 +3668,49 @@ public void DisplayOrEditAasEntityConceptDescription( text: cd.Id); if (this.context.StartFlyoverModal(uc)) { + var oldId = cd.Id; + var newId = uc.Text.Trim(); var res = false; try { + // check, if Submodel is sitting in Repo + var sideInfo = OnDemandListIdentifiable + .FindSideInfoInListOfIdentifiables( + env.ConceptDescriptions, cd.GetReference()); + if (sideInfo != null) + { + // in any case, update Id + sideInfo.Id = newId; + + // ask user for repo operation + if (AnyUiMessageBoxResult.Yes == await this.context.MessageBoxFlyoutShowAsync( + "Rename ConceptDescription in Repository as well? " + + "This operation can not be reverted!", + "Rename Identifiable", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + { + + // rename in repo + // (only the side info in the _specific_ endpoint gives information, in + // which repo the Indentifiables could be deleted) + var newEndpoint = await PackageContainerHttpRepoSubset + .AssistantRenameIdfsInRepo( + baseUri: PackageContainerHttpRepoSubset.GetBaseUri( + sideInfo.DesignatedEndpoint?.AbsoluteUri), + oldId: oldId, + newId: newId, + runtimeOptions: packages.CentralRuntimeOptions, + moreLog: true); + + Log.Singleton.Info("Rename in repo performed successfully."); + + // adopt in side info + sideInfo.QueriedEndpoint = newEndpoint; + sideInfo.DesignatedEndpoint = newEndpoint; + } + } + // rename var lrf = env.RenameIdentifiable( cd.Id, uc.Text); @@ -5766,6 +6078,8 @@ 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. + /// Note: Because of Blazor principles for display of components, this function or its subordinates + /// MUST NOT be async! /// public bool DisplayOrEditCommonEntity( PackageCentral.PackageCentral packages, diff --git a/src/AasxPackageLogic/DispEditHelperExtensions.cs b/src/AasxPackageLogic/DispEditHelperExtensions.cs index 96bcd2d51..722760c64 100644 --- a/src/AasxPackageLogic/DispEditHelperExtensions.cs +++ b/src/AasxPackageLogic/DispEditHelperExtensions.cs @@ -368,7 +368,6 @@ public void ExtensionHelperAddIdfReference( setValue?.Invoke(createInstance?.Invoke((string)v)); return new AnyUiLambdaActionNone(); }, - keyVertCenter: true, firstColumnWidth: firstColumnWidth, auxButtonTitles: !showButtons ? null : new[] { "Existing", "New", "Jump" }, auxButtonToolTips: !showButtons ? null : new[] { diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 7ea33c72b..aeb72742d 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -67,13 +67,17 @@ public class DispEditInjectAction public string[] auxTitles = null; public string[] auxToolTips = null; public Func auxLambda = null; + public Func> auxLambdaAsync = null; public DispEditInjectAction() { } - public DispEditInjectAction(string[] auxTitles, Func auxLambda) + public DispEditInjectAction(string[] auxTitles, + Func auxLambda, + Func> auxLambdaAsync) { this.auxTitles = auxTitles; this.auxLambda = auxLambda; + this.auxLambdaAsync = auxLambdaAsync; } public DispEditInjectAction(string[] auxTitles, string[] auxToolTips, @@ -360,6 +364,17 @@ public void DisplayOrEditEntitySideInfo( AddKeyValue(stack, "IdShort", "" + si.IdShort, repo: null); AddKeyValue(stack, "Id", "" + si.Id, repo: null); + AddKeyValue(stack, "Id endpoint", "" + si.Id, repo: null, + auxButtonTitle: "Copy", + auxButtonLambda: (i) => { + this.context?.ClipboardSet(new AnyUiClipboardData( + text: si.Id) + { }); + Log.Singleton.Info(StoredPrint.Color.Blue, "Id copied to clipboard."); + return new AnyUiLambdaActionNone(); + }, + auxButtonOverride: true); + AddKeyValue(stack, "Queried endpoint", "" + si.QueriedEndpoint?.ToString(), repo: null, auxButtonTitle: "Copy", auxButtonLambda: (i) => { @@ -371,7 +386,7 @@ public void DisplayOrEditEntitySideInfo( { this.context?.ClipboardSet(new AnyUiClipboardData( text: si.QueriedEndpoint.ToString()) - { }); + { }); Log.Singleton.Info(StoredPrint.Color.Blue, "Queried endpoint copied to clipboard."); } return new AnyUiLambdaActionNone(); @@ -389,13 +404,14 @@ public void DisplayOrEditEntitySideInfo( { this.context?.ClipboardSet(new AnyUiClipboardData( text: si.DesignatedEndpoint.ToString()) - { }); + { }); Log.Singleton.Info(StoredPrint.Color.Blue, "Designated endpoint copied to clipboard."); } return new AnyUiLambdaActionNone(); }, auxButtonOverride: true); } + public void DisplayOrEditEntityMissingSideInfo( AnyUiStackPanel stack, string key) @@ -451,6 +467,7 @@ public void DisplayOrEditEntityListOfExtension(AnyUiStackPanel stack, // public void DisplayOrEditEntityIdentifiable(AnyUiStackPanel stack, + AdminShellPackageEnvBase packageEnv, Aas.IEnvironment env, Aas.IIdentifiable identifiable, string templateForIdString, @@ -460,6 +477,10 @@ public void DisplayOrEditEntityIdentifiable(AnyUiStackPanel stack, if (stack == null || identifiable == null) return; + // special flags + var isDynEnv = packageEnv is AdminShellPackageDynamicFetchEnv; + var idReadOnly = isDynEnv && identifiable.Id?.HasContent() == true; + // members this.AddGroup(stack, "Identifiable:", levelColors.SubSection); @@ -496,20 +517,20 @@ public void DisplayOrEditEntityIdentifiable(AnyUiStackPanel stack, })) { AddKeyValueExRef( - stack, "id", identifiable, identifiable.Id, null, repo, + stack, "id", identifiable, identifiable.Id, null, + (idReadOnly) ? null : repo, v => { var dr = new DiaryReference(identifiable); string value = v as string; - bool duplicate = false; identifiable.Id = v as string; - //mlem this.AddDiaryEntry(identifiable, new DiaryEntryStructChange(), diaryReference: dr); return new AnyUiLambdaActionNone(); }, takeOverLambdaAction: new AnyUiLambdaActionRedrawAllElements(nextFocus: identifiable), + auxButtonOverride: true, auxButtonTitles: DispEditInjectAction.GetTitles(new[] { "Generate" }, injectToId), - auxButtonLambda: (i) => + auxButtonLambdaAsync: async (i) => { if (i == 0) { @@ -521,7 +542,11 @@ public void DisplayOrEditEntityIdentifiable(AnyUiStackPanel stack, } if (i >= 1) { - var la = injectToId?.auxLambda?.Invoke(i - 1); + AnyUiLambdaActionBase la = null; + if (injectToId?.auxLambda != null) + la = injectToId.auxLambda?.Invoke(i - 1); + if (injectToId?.auxLambdaAsync != null) + la = await injectToId.auxLambdaAsync?.Invoke(i - 1); return la; } return new AnyUiLambdaActionNone(); diff --git a/src/AasxPackageLogic/DispEditHelperMultiElement.cs b/src/AasxPackageLogic/DispEditHelperMultiElement.cs index 1c4143d18..4fd6d2e27 100644 --- a/src/AasxPackageLogic/DispEditHelperMultiElement.cs +++ b/src/AasxPackageLogic/DispEditHelperMultiElement.cs @@ -725,7 +725,8 @@ public void DisplayOrEditAasEntityMultipleElements( vesm2.theEnv?.Submodels, KeyTypes.Submodel, vesm2.theSubmodel.Id, vesm2.theSubmodel.GetReference(), ref sideInfo); else - if (x is VisualElementSubmodelRef vesmr2 && vesmr2?.theSubmodelRef?.IsValid() == true) + if (x is VisualElementSubmodelRef vesmr2 && vesmr2?.theSubmodelRef?.IsValid() == true + && vesmr2?.theSubmodel != null) AddToListOfKey( idfToDel, vesmr2.theEnv, vesmr2.theEnv?.Submodels, KeyTypes.Submodel, diff --git a/src/AasxPackageLogic/DispEditHelperSammModules.cs b/src/AasxPackageLogic/DispEditHelperSammModules.cs index 63e109f55..271eaa6c0 100644 --- a/src/AasxPackageLogic/DispEditHelperSammModules.cs +++ b/src/AasxPackageLogic/DispEditHelperSammModules.cs @@ -268,7 +268,6 @@ public void SammExtensionHelperAddSammReference( setValue?.Invoke(createInstance?.Invoke((string)v)); return new AnyUiLambdaActionNone(); }, - keyVertCenter: true, firstColumnWidth: firstColumnWidth, auxButtonTitles: !showButtons ? null : new[] { "Preset", "Existing", "New", "Jump" }, auxButtonToolTips: !showButtons ? null : new[] { diff --git a/src/AasxPackageLogic/IMainWindow.cs b/src/AasxPackageLogic/IMainWindow.cs index 6982d9e43..693477b51 100644 --- a/src/AasxPackageLogic/IMainWindow.cs +++ b/src/AasxPackageLogic/IMainWindow.cs @@ -73,7 +73,7 @@ public interface IMainWindow /// /// Redraw tree elements (middle), AAS entitty (right side) /// - void CommandExecution_RedrawAll(); + Task CommandExecution_RedrawAllAsync(); /// /// Redraw window title, AAS info?, entity view (right), element tree (middle) @@ -81,7 +81,7 @@ public interface IMainWindow /// Try remember which element was focussed and focus it after redrawing. /// Focus a new main data object attached to an tree element. /// If focussing, expand this item. - void RedrawAllAasxElements( + Task RedrawAllAasxElementsAsync( bool keepFocus = false, object nextFocusMdo = null, bool wishExpanded = true); @@ -90,7 +90,7 @@ void RedrawAllAasxElements( /// Based on save information, will redraw the AAS entity (element) view (right). /// /// Highlight field (for find/ replace) - void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightField = null); + Task RedrawElementViewAsync(DispEditHighlight.HighlightFieldInfo hightlightField = null); // REFACTOR: for later refactoring /// @@ -108,7 +108,7 @@ void RedrawAllAasxElements( /// Large extend. Basially redraws everything after new package has been loaded. /// /// Only tghe AUX package has been altered. - void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null); + Task RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null); /// /// This function serve as a kind of unified contact point for all kind @@ -124,7 +124,7 @@ void RedrawAllAasxElements( /// 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 - void UiLoadPackageWithNew( + Task UiLoadPackageWithNew( PackageCentralItem packItem, AdminShellPackageEnvBase takeOverEnv = null, string loadLocalFilename = null, diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index e61c10b3b..01dd0e327 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -70,6 +70,16 @@ protected class EditingLocation /// protected static string _userLastGetUrl = "http://???:51310"; + /// + /// Flag, if to remember the new identifiable base URI. + /// + public bool RememberNewIdentifiableBaseUri = false; + + /// + /// Base URI for new Identifiable elements, if remembered. + /// + public string RememberedNewIdentifiableBaseUriStr = null; + /// /// Display context with more features for UI /// @@ -113,7 +123,7 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( // create new AASX package PackageCentral.MainItem.New(); // redraw - MainWindow.CommandExecution_RedrawAll(); + await MainWindow.CommandExecution_RedrawAllAsync(); } catch (Exception ex) { @@ -174,10 +184,12 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( // do try { + // remember the base URI is always stopped before new manual operation + RememberNewIdentifiableBaseUri = false; + // save await PackageCentral.MainItem.SaveAsAsync(runtimeOptions: PackageCentral.CentralRuntimeOptions); - // backup if (Options.Curr.BackupDir != null) PackageCentral.MainItem.Container.BackupInDir( @@ -196,7 +208,7 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( MainWindow.CheckIfToFlushEvents(); // as saving changes the structure of pending supplementary files, re-display - MainWindow.RedrawAllAasxElements(keepFocus: true); + MainWindow.RedrawAllAasxElementsAsync(keepFocus: true); } catch (Exception ex) { @@ -267,6 +279,9 @@ public async Task CommandBinding_GeneralDispatchAnyUiDialogs( // save DisplayContextPlus.RememberForInitialDirectory(ucsf.TargetFileName); + // remember the base URI is always stopped before new manual operation + RememberNewIdentifiableBaseUri = false; + if (!forceLocal) { // leave it where it is @@ -290,7 +305,7 @@ await PackageCentral.MainItem.SaveAsAsync(targetFn, prefFmt: prefFmt, PackageContainerBase.BackupType.FullCopy); // as saving changes the structure of pending supplementary files, re-display - MainWindow.RedrawAllAasxElements(); + MainWindow.RedrawAllAasxElementsAsync(); // LRU? // record in LRU? @@ -349,7 +364,7 @@ await DisplayContextPlus.WebBrowserDisplayOrDownloadFile( try { PackageCentral.MainItem.Close(); - MainWindow.RedrawAllAasxElements(); + MainWindow.RedrawAllAasxElementsAsync(); } catch (Exception ex) { @@ -380,8 +395,8 @@ await DisplayContextPlus.WebBrowserDisplayOrDownloadFile( await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); // update - MainWindow.RedrawAllAasxElements(); - MainWindow.RedrawElementView(); + MainWindow.RedrawAllAasxElementsAsync(); + await MainWindow.RedrawElementViewAsync(); return; } @@ -928,8 +943,8 @@ await val.PerformDialogue(ticket, DisplayContext, await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); // update - MainWindow.RedrawAllAasxElements(); - MainWindow.RedrawElementView(); + MainWindow.RedrawAllAasxElementsAsync(); + await MainWindow.RedrawElementViewAsync(); } if (cmd == "submodelread") @@ -950,8 +965,8 @@ await val.PerformDialogue(ticket, DisplayContext, { await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); - MainWindow.RedrawAllAasxElements(); - MainWindow.RedrawElementView(); + MainWindow.RedrawAllAasxElementsAsync(); + await MainWindow.RedrawElementViewAsync(); } catch (Exception ex) { @@ -1059,8 +1074,8 @@ await DisplayContextPlus.CheckIfDownloadAndStart( await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); // redisplay - MainWindow.RedrawAllAasxElements(); - MainWindow.RedrawElementView(); + MainWindow.RedrawAllAasxElementsAsync(); + await MainWindow.RedrawElementViewAsync(); } catch (Exception ex) { @@ -1133,8 +1148,8 @@ await DisplayContextPlus.CheckIfDownloadAndStart( await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); // redisplay - MainWindow.RedrawAllAasxElements(); - MainWindow.RedrawElementView(); + MainWindow.RedrawAllAasxElementsAsync(); + await MainWindow.RedrawElementViewAsync(); } catch (Exception ex) { @@ -1161,8 +1176,8 @@ await DisplayContextPlus.CheckIfDownloadAndStart( await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); // redisplay - MainWindow.RedrawAllAasxElements(); - MainWindow.RedrawElementView(); + MainWindow.RedrawAllAasxElementsAsync(); + await MainWindow.RedrawElementViewAsync(); } catch (Exception ex) { @@ -1191,8 +1206,8 @@ await DisplayContextPlus.CheckIfDownloadAndStart( await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); // redisplay - MainWindow.RedrawAllAasxElements(); - MainWindow.RedrawElementView(); + MainWindow.RedrawAllAasxElementsAsync(); + await MainWindow.RedrawElementViewAsync(); } catch (Exception ex) { @@ -1249,8 +1264,8 @@ await DisplayContextPlus.CheckIfDownloadAndStart( await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); // redisplay - MainWindow.RedrawAllAasxElements(); - MainWindow.RedrawElementView(); + MainWindow.RedrawAllAasxElementsAsync(); + await MainWindow.RedrawElementViewAsync(); } catch (Exception ex) { @@ -1992,7 +2007,7 @@ private async Task CommandBinding_FixAndFinalizeAsync(AasxMenuActionTicket ticke MainWindow.CheckIfToFlushEvents(); // as saving changes the structure of pending supplementary files, re-display - MainWindow.RedrawAllAasxElements(keepFocus: true); + MainWindow.RedrawAllAasxElementsAsync(keepFocus: true); } catch (Exception ex) { @@ -2079,7 +2094,7 @@ public async Task UiLoadFileRepositoryAsync(string fn, /// Using the currently loaded AASX, will check if a CD_AasxLoadedNavigateTo elements can be /// found to be activated /// - public bool UiCheckIfActivateLoadedNavTo() + public async Task UiCheckIfActivateLoadedNavTo() { // access if (PackageCentral.Main?.AasEnv == null || MainWindow.GetDisplayElements() == null) @@ -2120,7 +2135,7 @@ public bool UiCheckIfActivateLoadedNavTo() LocationHistory?.Push(veFound); // fake selection - MainWindow.RedrawElementView(); + await MainWindow.RedrawElementViewAsync(); MainWindow.TakeOverContentEnable(false); MainWindow.UpdateDisplay(); diff --git a/src/AasxPackageLogic/MainWindowHeadless.cs b/src/AasxPackageLogic/MainWindowHeadless.cs index e04859d5a..ba625e84c 100644 --- a/src/AasxPackageLogic/MainWindowHeadless.cs +++ b/src/AasxPackageLogic/MainWindowHeadless.cs @@ -116,6 +116,7 @@ public void FillSelectedItem( ticket.Env = vesmr.theEnv; if (selectedItem != null) { + ticket.AAS = vesmr.theAas; ticket.Submodel = vesmr.theSubmodel; ticket.SubmodelRef = vesmr.theSubmodelRef; } @@ -1717,7 +1718,7 @@ record = o; else if (res is AasxPluginResultEventRedrawAllElements aprrae) { - MainWindow.CommandExecution_RedrawAll(); + MainWindow.CommandExecution_RedrawAllAsync(); } } catch (Exception ex) diff --git a/src/AasxPackageLogic/MainWindowScripting.cs b/src/AasxPackageLogic/MainWindowScripting.cs index f5ee13efa..13f19599f 100644 --- a/src/AasxPackageLogic/MainWindowScripting.cs +++ b/src/AasxPackageLogic/MainWindowScripting.cs @@ -515,7 +515,7 @@ public async Task Tool( // perform UI updates if required if (ticket.UiLambdaAction != null && !(ticket.UiLambdaAction is AnyUiLambdaActionNone)) { - // add to "normal" event quoue + // add to "normal" event queue MainWindow.AddWishForToplevelAction(ticket.UiLambdaAction); } diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index 975a8ea30..a059e479f 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -691,6 +691,9 @@ public AnyUiColor GetColor(ColorNames c) "delimited by double quotes.")] public string HttpHeaderAttributes = ""; + [OptionDescription(Description = "For connecting to repositories/ registry, default pagination limit.")] + public int DefaultConnectPageLimit = 10; + [OptionDescription(Description = "Point to a list of SecureConnectPresets for the respective dialogue")] [JetBrains.Annotations.UsedImplicitly] public Newtonsoft.Json.Linq.JToken SecureConnectPresets; diff --git a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs index 4cf91a7b8..e53ca695a 100644 --- a/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs +++ b/src/AasxPackageLogic/PackageCentral/AasOnDemandEnvironment.cs @@ -27,6 +27,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Collections.Generic; using Newtonsoft.Json; using System.Linq; +using Lucene.Net.Util; namespace AasxPackageLogic.PackageCentral { @@ -71,6 +72,13 @@ public class AasIdentifiableSideInfo : OnDemandSideInfoBase public class OnDemandListIdentifiable : OnDemandList, IClass where T : Aas.IIdentifiable { + public OnDemandListIdentifiable() { } + + public OnDemandListIdentifiable(IEnumerable items) + { + this.AddRange(items); + } + public int FindSideInfoIndexFromId(string id) { if (id?.HasContent() != true) @@ -106,7 +114,7 @@ public static AasIdentifiableSideInfo FindSideInfoInListOfIdentifiables( return smsi.GetSideInfo(ndx); } return null; - } + } // // make this class suitable for IClass as well diff --git a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs index fa57b2ff7..1a7653066 100644 --- a/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs +++ b/src/AasxPackageLogic/PackageCentral/AdminShellPackageDynamicFetchEnv.cs @@ -7,26 +7,27 @@ 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.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.Collections.Generic; -using System.Linq; +using System.Windows.Forms.Design; +using AasxIntegrationBase; +using AasxOpenIdClient; +using AdminShellNS; using AdminShellNS.DiaryData; -using System.Text.Json; +using Extensions; +using IdentityModel.Client; +using Aas = AasCore.Aas3_0; namespace AasxPackageLogic.PackageCentral { @@ -263,7 +264,7 @@ public async Task TryFetchAllMissingIdentifiables( protected async Task TrySaveAllTaintedIdentifiablesOf( object listOfIdf, - Func lambdaBuildRessourceForNoEndpoint, + Func> lambdaGetBaseUriForNewIdentifiables = null, bool clearTaintedFlags = true) where T : Aas.IIdentifiable { var list = listOfIdf as OnDemandListIdentifiable; @@ -271,52 +272,93 @@ protected async Task TrySaveAllTaintedIdentifiablesOf( return 0; var count = 0; - for (int i = 0; i < list.Count(); i++) + for (int listNdx = 0; listNdx < list.Count(); listNdx++) { -#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]; + var idf = list[listNdx]; if (idf == null) // surprising :-/ continue; - // tainted? (in doubt, yes) + // Only concern on tainted data var tidf = idf as ITaintedData; if (tidf?.TaintedData != null && tidf.TaintedData.Tainted == null) continue; - // try save, need a REST ressource. Either use the existing endpoint - // or build the uri. + // Prepare building URI for REST ressource Uri uri = null; - if (si.StubLevel >= AasIdentifiableSideInfoLevel.IdWithEndpoint && si.DesignatedEndpoint != null) - uri = si.DesignatedEndpoint; - else - if (si.StubLevel >= AasIdentifiableSideInfoLevel.IdWithEndpoint && si.QueriedEndpoint != null) - uri = si.QueriedEndpoint; - else - uri = lambdaBuildRessourceForNoEndpoint(_defaultRepoBaseUri, idf.Id); + Uri futureEP = null; + bool usePost = false; + bool buildSideInfo = false; + + // Get the side info. + var si = list.GetSideInfo(listNdx); + if (si == null) + { + // no side info, so this might be a new Identifiable + // Investigate further, need to get a "positive info" from tghe user (lambda) + Uri workBaseUri = null; + if (lambdaGetBaseUriForNewIdentifiables != null) + workBaseUri = await lambdaGetBaseUriForNewIdentifiables.Invoke(_defaultRepoBaseUri, idf); + if (workBaseUri == null || !workBaseUri.IsAbsoluteUri) + { + // skip Identifiable + continue; + } + else + { + // build the uri + // assume no collision, so use POST + usePost = true; + uri = PackageContainerHttpRepoSubset.BuildUriForRepoSingleIdentifiable( + workBaseUri, idf.Id, usePost: true, encryptIds: true); + buildSideInfo = true; + futureEP = PackageContainerHttpRepoSubset.BuildUriForRepoSingleIdentifiable( + workBaseUri, idf.Id, usePost: false, encryptIds: true); + + if (uri == null) + { + // skip Identifiable + continue; + } + } + } + + if (si != null && si.IsStub) + { + // Continue (for the time being), if side info present (was dynamically fetched) but + // is a stub to a registry + continue; + } + if (uri == null) + { + // try use the existing endpoint + if (si.StubLevel >= AasIdentifiableSideInfoLevel.IdWithEndpoint && si.DesignatedEndpoint != null) + uri = si.DesignatedEndpoint; + else + if (si.StubLevel >= AasIdentifiableSideInfoLevel.IdWithEndpoint && si.QueriedEndpoint != null) + uri = si.QueriedEndpoint; + else + uri = PackageContainerHttpRepoSubset + .BuildUriForRepoSingleIdentifiable(_defaultRepoBaseUri, idf.Id, encryptIds: true); + } + + if (uri == null) + { + _runtimeOptions?.Log?.Error("Could not determine URI for saving Identifiable {0} with id {1}", + typeof(T).Name, idf.Id); continue; + } // serialize to memory stream var res2 = await PackageHttpDownloadUtil.HttpPutPostIdentifiable( reUseClient: null, + usePost: usePost, idf: idf, destUri: uri); - if (res2 == null || ((res2.Item1 != HttpStatusCode.OK) && (res2.Item1 != HttpStatusCode.NoContent))) + + if (res2 == null || !((int)res2.Item1 >= 200 && (int)res2.Item1 <= 299)) { _runtimeOptions?.Log?.Error("Save of modified Identifiable returned error {0} for id={1} at {2}", "" + ((res2 != null) ? (int)res2.Item1 : -1), @@ -328,30 +370,85 @@ protected async Task TrySaveAllTaintedIdentifiablesOf( // clear the tainted flag if (clearTaintedFlags && tidf?.TaintedData != null) tidf.TaintedData.Tainted = null; + + // may be new, build side info + if (buildSideInfo && futureEP != null) + { + // build side info + si = new AasIdentifiableSideInfo() + { + IsStub = false, + StubLevel = AasIdentifiableSideInfoLevel.IdWithEndpoint, + Id = idf.Id, + IdShort = idf.IdShort, + QueriedEndpoint = futureEP, + DesignatedEndpoint = futureEP + }; + + // add to list + list.SetSideInfo(listNdx, si); + } } } return count; } + /// + /// As the requirement is, that a list of Identifiables is null, if empty and most of the + /// functionality (AasxCSharpLibrary and beyond) is not aware, that the list of Identifiables + /// should be OnDemandListIdentifiable, these functionality might create an + /// ordinary list of Identifiables, not fullfilling the requirements. + /// This function tries to fix it. + /// + public void FixIdfListsToBeOnDemandLists() + { + if (_aasEnv.AssetAdministrationShells is not null + && _aasEnv.AssetAdministrationShells is not OnDemandListIdentifiable) + { + // fix + _aasEnv.AssetAdministrationShells = new OnDemandListIdentifiable( + _aasEnv.AssetAdministrationShells); + } + + if (_aasEnv.Submodels is not null + && _aasEnv.Submodels is not OnDemandListIdentifiable) + { + // fix + _aasEnv.Submodels = new OnDemandListIdentifiable( + _aasEnv.Submodels); + } + + if (_aasEnv.ConceptDescriptions is not null + && _aasEnv.ConceptDescriptions is not OnDemandListIdentifiable) + { + // fix + _aasEnv.ConceptDescriptions = new OnDemandListIdentifiable( + _aasEnv.ConceptDescriptions); + } + } + public async Task TrySaveAllTaintedIdentifiables( bool allAas = true, bool allSubmodels = true, - bool allCDs = true) + bool allCDs = true, + Func> lambdaGetBaseUriForNewIdentifiables = null) { var count = 0; - + + FixIdfListsToBeOnDemandLists(); + if (allAas) count += await TrySaveAllTaintedIdentifiablesOf( _aasEnv?.AssetAdministrationShells, - (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForRepoSingleAAS(defBase, id)); + lambdaGetBaseUriForNewIdentifiables: lambdaGetBaseUriForNewIdentifiables); if (allSubmodels) count += await TrySaveAllTaintedIdentifiablesOf( _aasEnv?.Submodels, - (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForRepoSingleSubmodel(defBase, id)); + lambdaGetBaseUriForNewIdentifiables: lambdaGetBaseUriForNewIdentifiables); if (allCDs) count += await TrySaveAllTaintedIdentifiablesOf( _aasEnv?.ConceptDescriptions, - (defBase, id) => PackageContainerHttpRepoSubset.BuildUriForRepoSingleCD(defBase, id)); + lambdaGetBaseUriForNewIdentifiables: lambdaGetBaseUriForNewIdentifiables); return count; } @@ -380,6 +477,22 @@ public override byte[] GetThumbnailBytesFromAasOrPackage(string aasId) return base.GetThumbnailBytesFromAasOrPackage(aasId); } + public void RenameThumbnailData(string oldId, string newId) + { + // access + if (oldId?.HasContent() != true || newId?.HasContent() != true) + return; + // rename + lock (_thumbStreamPerAasId) + { + if (_thumbStreamPerAasId.ContainsKey(oldId)) + { + _thumbStreamPerAasId[newId] = _thumbStreamPerAasId[oldId]; + _thumbStreamPerAasId.Remove(oldId); + } + } + } + public override async Task GetBytesFromPackageOrExternalAsync( string uriString, string aasId = null, diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs index 8b324215b..5de1903bc 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerBase.cs @@ -102,6 +102,12 @@ public delegate AnyUiMessageBoxResult ShowMessageDelegate( /// specific configuration or user UI invocation. /// public ISecurityAccessHandler SecurityAccessHandler = null; + + /// + /// If set, allows to realize a user interaction to specifiy, if Identifiables shall be + /// uploaded to a remote repository or not and to which base URI this should happen. + /// + public Func> GetBaseUriForNewIdentifiablesHandler = null; } /// diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs index 6872434ab..8f05ee70d 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerFactory.cs @@ -246,6 +246,10 @@ public static async Task GuessAndCreateForAsync( { extCntOpt.Record.AutoLoadSubmodels = true; extCntOpt.Record.AutoLoadOnDemand = false; + + // also, set base type and adress + extCntOpt.Record.BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository; + extCntOpt.Record.BaseAddress = "" + PackageContainerHttpRepoSubset.GetBaseUri(location)?.AbsoluteUri; } // prepare runtime options diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs index 36776eba0..bfa5e2456 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs @@ -7,32 +7,34 @@ 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.Collections.Generic; +using System.ComponentModel; +using System.Dynamic; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Text; +using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.Collections.Generic; +using System.Web; +using AasxIntegrationBase; +using AasxOpenIdClient; using AasxPackageExplorer; +using AdminShellNS; +using AngleSharp.Dom; using AnyUi; -using System.Text.Json.Nodes; -using System.Linq; -using System.Web; +using Extensions; +using IdentityModel.Client; using Newtonsoft.Json; -using System.Dynamic; using Newtonsoft.Json.Linq; -using System.ComponentModel; +using RestSharp; +using Aas = AasCore.Aas3_0; namespace AasxPackageLogic.PackageCentral { @@ -167,7 +169,7 @@ public static bool IsValidUriForRepoAllAAS(string location) public static bool IsValidUriForRepoSingleAAS(string location) { - var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells/([^?]{1,999})", + var m = Regex.Match(location, @"^(http(|s))://(.*?)/shells/([^?]{1,999})", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); return m.Success; } @@ -181,7 +183,7 @@ public static bool IsValidUriForRepoAllSubmodel(string location) public static bool IsValidUriForRepoSingleSubmodel(string location) { - var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels/(.{1,999})$", + var m = Regex.Match(location, @"^(http(|s))://(.*?)/submodels/(.{1,999})$", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); if (m.Success) return true; @@ -414,8 +416,22 @@ public static Uri BuildUriForRepoAllAAS(Uri baseUri, int pageLimit = 100, string return uri.Uri; } + public static Uri BuildUriForRepoSingleIdentifiable( + Uri baseUri, string id, + bool encryptIds = true, + bool usePost = false) where T : Aas.IIdentifiable + { + if (typeof(T).IsAssignableFrom(typeof(Aas.IAssetAdministrationShell))) + return BuildUriForRepoSingleAAS(baseUri, id, encryptIds: encryptIds, usePost: usePost); + if (typeof(T).IsAssignableFrom(typeof(Aas.ISubmodel))) + return BuildUriForRepoSingleSubmodel(baseUri, id, encryptIds: encryptIds, usePost: usePost); + if (typeof(T).IsAssignableFrom(typeof(Aas.IConceptDescription))) + return BuildUriForRepoSingleCD(baseUri, id, encryptIds: encryptIds, usePost: usePost); + return null; + } + public static Uri BuildUriForRepoSingleAAS( - Uri baseUri, string id, + Uri baseUri, string id, bool encryptIds = true, bool usePost = false) { @@ -479,7 +495,7 @@ public static Uri BuildUriForRepoAllSubmodel(Uri baseUri, int pageLimit = 100, s } public static Uri BuildUriForRepoSingleSubmodel( - Uri baseUri, string id, + Uri baseUri, string id, bool encryptIds = true, bool usePost = false, bool addAasId = false, @@ -572,7 +588,7 @@ public static Uri BuildUriForRepoAllCD(Uri baseUri, int pageLimit = 100, string } public static Uri BuildUriForRepoSingleCD( - Uri baseUri, string id, + Uri baseUri, string id, bool encryptIds = true, bool usePost = false) { @@ -605,7 +621,7 @@ public static Uri BuildUriForRepoQuery(Uri baseUri, string query) // 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 queryEnc = AdminShellUtil.Base64UrlEncode(query); var uri = new UriBuilder(CombineUri(baseUri, $"graphql")); uri.Query = $"query={queryEnc}"; return uri.Uri; @@ -733,7 +749,7 @@ public static ProfileDescription FindProfileDescription(string input) if (pd.Id.Equals(input)) return pd; return null; - } + } // // ALL @@ -744,14 +760,14 @@ protected class FetchItem { public FetchItemType Type; public string Value; - } - - private static async Task FromRegistryGetAasAndSubmodels( - OnDemandListIdentifiable prepAas, + } + + private static async Task FromRegistryGetAasAndSubmodels( + OnDemandListIdentifiable prepAas, OnDemandListIdentifiable prepSM, ConnectExtendedRecord record, PackCntRuntimeOptions runtimeOptions, - bool allowFakeResponses, + bool allowFakeResponses, dynamic aasDescriptor, List trackNewIdentifiables = null, List trackLoadedIdentifiables = null, @@ -849,7 +865,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); @@ -915,12 +931,12 @@ private static async Task FromRegistryGetAasAndSubmodels( return true; } - private static async Task FromRegOfRegGetAasAndSubmodels( - OnDemandListIdentifiable prepAas, + private static async Task FromRegOfRegGetAasAndSubmodels( + OnDemandListIdentifiable prepAas, OnDemandListIdentifiable prepSM, ConnectExtendedRecord record, PackCntRuntimeOptions runtimeOptions, - bool allowFakeResponses, + bool allowFakeResponses, dynamic regDescriptor, string assetId, List trackNewIdentifiables = null, @@ -952,9 +968,9 @@ private static async Task FromRegOfRegGetAasAndSubmodels( // valid url? if (regUrl == "") return false; - + var basicUri = GetBaseUri(regUrl); - if (basicUri == null) + if (basicUri == null) return false; // build again a set of baseUris, but only one pattern set @@ -1067,7 +1083,7 @@ protected static async Task LoadFromSourceInternalAsyn int numSM = 0; int numCD = 0; int numDiv = 0; - + Action lambdaReportAll = (nAas, nSm, nCd, nDiv) => { runtimeOptions?.ProgressChanged(PackCntRuntimeOptions.Progress.OverallMessage, @@ -1119,7 +1135,7 @@ protected static async Task LoadFromSourceInternalAsyn // integrate in a fresh environment // TODO: new kind of environment - env = (Aas.IEnvironment) new AasOnDemandEnvironment(); + env = (Aas.IEnvironment)new AasOnDemandEnvironment(); // already set structure to use some convenience functions env.AssetAdministrationShells = prepAas; @@ -1128,7 +1144,7 @@ protected static async Task LoadFromSourceInternalAsyn // also the package "around" // TODO: Check default for base uri - dynPack = new AdminShellPackageDynamicFetchEnv(runtimeOptions, + dynPack = new AdminShellPackageDynamicFetchEnv(runtimeOptions, baseUri.GetBaseUriForAasRepo()); } @@ -1171,7 +1187,7 @@ protected static async Task LoadFromSourceInternalAsyn { // refer to dedicated function await FromRegOfRegGetAasAndSubmodels( - prepAas, prepSM, record, runtimeOptions, allowFakeResponses, + prepAas, prepSM, record, runtimeOptions, allowFakeResponses, res, foundAssetId, trackNewIdentifiables, trackLoadedIdentifiables, lambdaReportProgress: lambdaReportAasSm, @@ -1443,6 +1459,34 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( if (idf == null) continue; + // only here, filtering may happen + var ct = (record.FilterCaseInvariant) ? StringComparison.InvariantCultureIgnoreCase + : StringComparison.InvariantCulture; + if (record.FilterByText && record.FilterText?.HasContent() == true) + { + var hit = idf.Id?.Contains(record.FilterText, ct) == true + || idf.IdShort?.Contains(record.FilterText, ct) == true + || idf.Description?.Contains(record.FilterText, ct) == true + || idf.DisplayName?.Contains(record.FilterText, ct) == true; + if (!hit) + continue; + } + + if (record.FilterByExtension && record.FilterExtName?.HasContent() == true) + { + var hit = false; + if (idf.Extensions?.IsValid() == true) + foreach (var ext in idf.Extensions) + if (ext?.Name?.Contains(record.FilterExtName, ct) == true) + // an empty search value counts as a hit + if (record.FilterExtName?.HasContent() != true) + hit = true; + else + hit = hit || (ext.Value?.Contains("" + record.FilterExtValue, ct) == true); + if (!hit) + continue; + } + // on last child, attach side info for fetch prev/ next cursor AasIdentifiableSideInfo si = new AasIdentifiableSideInfo() { @@ -1919,7 +1963,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var lrs = env.FindAllSubmodelReferences(onlyNotExisting: true).ToList(); await Parallel.ForEachAsync(lrs, - new ParallelOptions() { + new ParallelOptions() + { MaxDegreeOfParallelism = record?.ParallelReads ?? Options.Curr.MaxParallelReadOps, CancellationToken = runtimeOptions?.CancellationTokenSource?.Token ?? CancellationToken.None }, @@ -1993,7 +2038,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( // start auto-load missing thumbnails? if (operationFound && (record?.AutoLoadThumbnails ?? false)) await Parallel.ForEachAsync(env.AllAssetAdministrationShells(), - new ParallelOptions() { + new ParallelOptions() + { MaxDegreeOfParallelism = record?.ParallelReads ?? Options.Curr.MaxParallelReadOps, CancellationToken = runtimeOptions?.CancellationTokenSource?.Token ?? CancellationToken.None }, @@ -2028,7 +2074,8 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( var lrs = env.FindAllReferencedSemanticIds().ToList(); await Parallel.ForEachAsync(lrs, - new ParallelOptions() { + new ParallelOptions() + { MaxDegreeOfParallelism = record?.ParallelReads ?? Options.Curr.MaxParallelReadOps, CancellationToken = runtimeOptions?.CancellationTokenSource?.Token ?? CancellationToken.None }, @@ -2179,7 +2226,7 @@ await PackageHttpDownloadUtil.HttpGetToMemoryStream( return targetEnv; } - + public override async Task SaveLocalCopyAsync( string targetFilename, PackCntRuntimeOptions runtimeOptions = null) @@ -2206,16 +2253,24 @@ public override async Task SaveToSourceAsync(string saveAsNewFileName = null, } // refer to dynPack - await dynPack.TrySaveAllTaintedIdentifiables(); + await dynPack.TrySaveAllTaintedIdentifiables( + lambdaGetBaseUriForNewIdentifiables: + (runtimeOptions?.GetBaseUriForNewIdentifiablesHandler != null) + ? runtimeOptions.GetBaseUriForNewIdentifiablesHandler + : async (defBase, idf) => + { + await Task.Yield(); + return defBase; + }); } // // UI // - public class ConnectExtendedRecord + public class ConnectExtendedRecord { - public ConnectExtendedRecord() + public ConnectExtendedRecord() { HeaderAttributes = Options.Curr.HttpHeaderAttributes; } @@ -2242,7 +2297,7 @@ public enum BaseTypeEnum { Repository, Registry, RegOfReg } [AasxMenuArgument(help: "Get a single AAS, which is specified by AasId.")] public bool GetSingleAas; - [AasxMenuArgument(help: "Specicies the Id of the AAS to be retrieved.")] + [AasxMenuArgument(help: "Specifies 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"; @@ -2250,7 +2305,7 @@ public enum BaseTypeEnum { Repository, Registry, RegOfReg } [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.")] + [AasxMenuArgument(help: "Specifies the Id of the asset to be retrieved.")] public string AssetId = ""; // public string AssetId = "https://pk.harting.com/?.20P=ZSN1"; @@ -2261,7 +2316,7 @@ public enum BaseTypeEnum { Repository, Registry, RegOfReg } [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.")] + [AasxMenuArgument(help: "Specifies the Id of the Submodel to be retrieved.")] // public string SmId = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vMjAxNV82MDIwXzMwMTJfMDU4NQ=="; public string SmId = ""; @@ -2272,18 +2327,36 @@ public enum BaseTypeEnum { Repository, Registry, RegOfReg } [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.")] + [AasxMenuArgument(help: "Specifies 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. " + + [AasxMenuArgument(help: "Specifies 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: "Filter elements on (Id, IdShort, DisplayName, Description) after getting.")] + public bool FilterByText; + + [AasxMenuArgument(help: "Specifies the text to filter for.")] + public string FilterText = ""; + + [AasxMenuArgument(help: "Filter elements on a specific extension name and value after getting.")] + public bool FilterByExtension; + + [AasxMenuArgument(help: "Specifies the name of extension to filter for.")] + public string FilterExtName = ""; + + [AasxMenuArgument(help: "Specifies the value of extension to filter for.")] + public string FilterExtValue = ""; + + [AasxMenuArgument(help: "Specifies the filtering to be case-invariant.")] + public bool FilterCaseInvariant = true; + [AasxMenuArgument(help: "When a AAS is retrieved, try to retrieve Submodels as well. " + "Note: For this retrieveal, AutoLoadOnDemand may apply.")] public bool AutoLoadSubmodels = true; @@ -2296,7 +2369,7 @@ public enum BaseTypeEnum { Repository, Registry, RegOfReg } [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 (false) or just create a side-information for later fetch (true).")] public bool AutoLoadOnDemand = false; @@ -2318,12 +2391,12 @@ public enum BaseTypeEnum { Repository, Registry, RegOfReg } /// Pagenation. Limit to n results. /// [AasxMenuArgument(help: "Pagenation. Limit to n results.")] - public int PageLimit = 4; + public int PageLimit = Options.Curr.DefaultConnectPageLimit; /// /// When fetching, skip first n elements of the results. /// - [AasxMenuArgument(help: "When fetching, skip first n elements of the results.")] + [AasxMenuArgument(help: "When fetching, skip first n elements of the results.")] public int PageSkip = 0; /// @@ -2345,10 +2418,11 @@ public enum BaseTypeEnum { Repository, Registry, RegOfReg } /// public HttpHeaderData HeaderData = new(); - public enum QueryChoice { - None, - AllAas, - SingleAas, + public enum QueryChoice + { + None, + AllAas, + SingleAas, AasByAssetLink, AllSM, SingleSM, @@ -2486,7 +2560,7 @@ public static BasedLocation BuildLocationFrom( if (record.GetAllAas) { // if a skip has been requested, these AAS need to be loaded, as well - var uri = BuildUriForRepoAllAAS(baseUris.GetBaseUriForAasRepo(), + var uri = BuildUriForRepoAllAAS(baseUris.GetBaseUriForAasRepo(), record.PageLimit + record.PageSkip, cursor); return new BasedLocation(baseUris, uri); } @@ -2494,7 +2568,7 @@ public static BasedLocation BuildLocationFrom( // Single AAS? if (record.GetSingleAas) { - var uri = BuildUriForRepoSingleAAS(baseUris.GetBaseUriForAasRepo(), + var uri = BuildUriForRepoSingleAAS(baseUris.GetBaseUriForAasRepo(), record.AasId, encryptIds: record.EncryptIds); return new BasedLocation(baseUris, uri); } @@ -2511,7 +2585,7 @@ public static BasedLocation BuildLocationFrom( if (record.GetAllSubmodel) { // if a skip has been requested, these AAS need to be loaded, as well - var uri = BuildUriForRepoAllSubmodel(baseUris.GetBaseUriForSmRepo(), + var uri = BuildUriForRepoAllSubmodel(baseUris.GetBaseUriForSmRepo(), record.PageLimit + record.PageSkip, cursor); return new BasedLocation(baseUris, uri); } @@ -2519,7 +2593,7 @@ public static BasedLocation BuildLocationFrom( // Single Submodel? if (record.GetSingleSubmodel) { - var uri = BuildUriForRepoSingleSubmodel(baseUris.GetBaseUriForSmRepo(), + var uri = BuildUriForRepoSingleSubmodel(baseUris.GetBaseUriForSmRepo(), record.SmId, encryptIds: record.EncryptIds); return new BasedLocation(baseUris, uri); } @@ -2527,7 +2601,7 @@ public static BasedLocation BuildLocationFrom( // All Submodels? if (record.GetAllCD) { - var uri = BuildUriForRepoAllCD(baseUris.GetBaseUriForCdRepo(), + var uri = BuildUriForRepoAllCD(baseUris.GetBaseUriForCdRepo(), record.PageLimit + record.PageSkip, cursor); return new BasedLocation(baseUris, uri); } @@ -2535,7 +2609,7 @@ public static BasedLocation BuildLocationFrom( // Single CD? if (record.GetSingleCD) { - var uri = BuildUriForRepoSingleCD(baseUris.GetBaseUriForCdRepo(), + var uri = BuildUriForRepoSingleCD(baseUris.GetBaseUriForCdRepo(), record.CdId, encryptIds: record.EncryptIds); return new BasedLocation(baseUris, uri); } @@ -2559,7 +2633,7 @@ public static BasedLocation BuildLocationFrom( if (record.GetAllAas) { // if a skip has been requested, these AAS need to be loaded, as well - var uri = BuildUriForRegistryAllAAS(baseUris.GetBaseUriForAasReg(), + var uri = BuildUriForRegistryAllAAS(baseUris.GetBaseUriForAasReg(), record.PageLimit + record.PageSkip, cursor); return new BasedLocation(baseUris, uri); } @@ -2567,7 +2641,7 @@ public static BasedLocation BuildLocationFrom( // Single AAS? if (record.GetSingleAas) { - var uri = BuildUriForRegistrySingleAAS(baseUris.GetBaseUriForAasReg(), + var uri = BuildUriForRegistrySingleAAS(baseUris.GetBaseUriForAasReg(), record.AasId, encryptIds: record.EncryptIds); return new BasedLocation(baseUris, uri); } @@ -2604,15 +2678,17 @@ public static BasedLocation BuildLocationFrom( return null; } - public enum ConnectExtendedScope { - All = 0xffff, + public enum ConnectExtendedScope + { + All = 0xffff, BaseInfo = 0x0001, IdfTypes = 0x0002, Query = 0x004, GetOptions = 0x0008, - StayConnected = 0x0010, - Pagination = 0x0020, - Header = 0x0040 + Filters = 0x0010, + StayConnected = 0x0020, + Pagination = 0x0040, + Header = 0x0080 } public static async Task PerformConnectExtendedDialogue( @@ -2624,7 +2700,7 @@ public static async Task PerformConnectExtendedDialogue( { // access if (displayContext == null || caption?.HasContent() != true || record == null) - return false ; + return false; // if a target file is given, a headless operation occurs #if __later @@ -2680,7 +2756,7 @@ public static async Task PerformConnectExtendedDialogue( var panel = new AnyUiStackPanel(); var helper = new AnyUiSmallWidgetToolkit(); - var g = helper.AddSmallGrid(25, 2, new[] { "120:", "*" }, + var g = helper.AddSmallGrid(35, 2, new[] { "120:", "*" }, padding: new AnyUiThickness(0, 5, 0, 5), margin: new AnyUiThickness(10, 0, 30, 0)); @@ -2741,6 +2817,8 @@ public static async Task PerformConnectExtendedDialogue( if ((scope & ConnectExtendedScope.IdfTypes) > 0) { + helper.AddSmallSeparatorToRowPlus(g, ref row); + // All AASes AnyUiUIElement.RegisterControl( helper.Set( @@ -2963,8 +3041,48 @@ public static async Task PerformConnectExtendedDialogue( row += 2; } + if ((scope & ConnectExtendedScope.Filters) > 0) + { + helper.AddSmallSeparatorToRowPlus(g, ref row); + + helper.AddSmallLabelAndCheckboxToRowPlus(g, ref row, null, "Filter \"Get all ..\" by text", + isChecked: record.FilterByText, setValue: (b) => record.FilterByText = b); + + helper.AddSmallLabelAndTextToRowPlus(g, ref row, "Filter text:", + record.FilterText, setValue: (s) => record.FilterText = s); + + helper.AddSmallLabelAndCheckboxToRowPlus(g, ref row, null, "Filter elements by extension name / value ..", + isChecked: record.FilterByExtension, setValue: (b) => record.FilterByExtension = b); + + // Extension label / name / value + helper.AddSmallLabelTo(g, row, 0, content: "Extension:", verticalCenter: true); + + var g2 = helper.AddSmallGridTo(g, row, 1, 1, 3, new[] { "*", "#", "*" }); + + AnyUiUIElement.SetStringFromControl( + helper.AddSmallTextBoxTo(g2, 0, 0, + text: $"{record.FilterExtName}", verticalCenter: true), + (s) => { record.FilterExtName = s; }); + + helper.AddSmallLabelTo(g2, 0, 1, content: " / ", verticalCenter: true); + + AnyUiUIElement.SetStringFromControl( + helper.AddSmallTextBoxTo(g2, 0, 2, + text: $"{record.FilterExtValue}", verticalCenter: true), + (s) => { record.FilterExtValue = s; }); + + row++; + + // last flag + + helper.AddSmallLabelAndCheckboxToRowPlus(g, ref row, "Options:", "Filter case-invariant", + isChecked: record.FilterCaseInvariant, setValue: (b) => record.FilterCaseInvariant = b); + } + if ((scope & ConnectExtendedScope.GetOptions) > 0) { + helper.AddSmallSeparatorToRowPlus(g, ref row); + // Auto load Submodels helper.AddSmallLabelTo(g, row, 0, content: "For above:", verticalAlignment: AnyUiVerticalAlignment.Center, @@ -3085,7 +3203,7 @@ public static async Task PerformConnectExtendedDialogue( } if ((scope & ConnectExtendedScope.Pagination) > 0) - { + { // Pagination helper.AddSmallLabelTo(g, row, 0, content: "Pagination:", verticalAlignment: AnyUiVerticalAlignment.Center, @@ -3162,7 +3280,7 @@ public static async Task PerformConnectExtendedDialogue( // ok return true; - } + } public class FileElementRecord { @@ -3221,7 +3339,8 @@ public static List FindAllUsedFileElements( } // now add - res.Add(new FileElementRecord() { + res.Add(new FileElementRecord() + { Parents = parents.Copy(), FileSme = file, IdShortPath = idsp @@ -3293,7 +3412,7 @@ public static async Task PerformUploadAssistant( // // Screen 1 : Job attributes // - + var recordJob = new UploadAssistantJobRecord(); ticket?.ArgValue?.PopulateObjectFromArgs(recordJob); @@ -3326,8 +3445,8 @@ public static async Task PerformUploadAssistant( row += 3; // separation - helper.AddSmallBorderTo(g, row, 0, - borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White, + helper.AddSmallBorderTo(g, row, 0, + borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White, colSpan: 2, margin: new AnyUiThickness(0, 0, 0, 20)); row++; @@ -3463,14 +3582,15 @@ public static async Task PerformUploadAssistant( return 3; return 4; }; - sorted.Sort((i1, i2) => { + 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; @@ -3480,13 +3600,14 @@ public static async Task PerformUploadAssistant( (!(idf is Aas.ISubmodel) || recordJob.IncludeSubmodels) && (!(idf is Aas.IConceptDescription) || recordJob.IncludeCDs) )) - .Select((idf) => new AnyUiDialogueDataGridRow() { + .Select((idf) => new AnyUiDialogueDataGridRow() + { Tag = idf, Cells = (new[] { // Type, IdShort, Id, Ver. Local, Method, Ver. Server "" + idf?.GetSelfDescription()?.ElementAbbreviation, "" + idf?.IdShort, - "" + idf?.Id, + "" + idf?.Id, "" + (idf?.Administration?.ToStringExtended(1) ?? "-"), "POST?", "-" @@ -3562,7 +3683,8 @@ public static async Task PerformUploadAssistant( Action lambdaStat = (found) => { - if (found) { numOK++; } else { numNOK++; }; + 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)); @@ -3717,7 +3839,7 @@ public static async Task PerformUploadAssistant( // if (recordJob.IncludeThumbFiles - && idf is Aas.IAssetAdministrationShell aas + && idf is Aas.IAssetAdministrationShell aas && aas.AssetInformation?.DefaultThumbnail?.Path?.HasContent() == true) { // try read the bytes (has NO try/catch in it) @@ -3800,7 +3922,7 @@ public static async Task PerformUploadAssistant( // attachments (for Submodels) // - if (recordJob.IncludeSupplFiles + if (recordJob.IncludeSupplFiles && idf is Aas.ISubmodel submodel && submodel.SubmodelElements != null) { // Note: the Part 2 PDF says '/', the swagger says '.' @@ -3834,7 +3956,7 @@ public static async Task PerformUploadAssistant( try { ba = await packEnv.GetBytesFromPackageOrExternalAsync(fn); - } + } catch (Exception ex) { LogInternally.That.SilentlyIgnoredError(ex); @@ -3898,7 +4020,7 @@ public static async Task PerformUploadAssistant( } catch (Exception ex) { - Log.Singleton.Error(ex, + Log.Singleton.Error(ex, $"when PUTting attachment with {ba.Length} bytes to File element {filEl.IdShortPath}"); numAttNOK++; } @@ -3913,7 +4035,8 @@ public static async Task PerformUploadAssistant( { Action lambdaStat = (ok) => { - if (ok) { numOK++; } else { numNOK++; }; + 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}"; @@ -4062,7 +4185,7 @@ public static async Task AssistantDeleteIdfsInRepo( // Statistics helper.AddSmallLabelTo(g, row, 0, content: "Requested to delete:"); - helper.AddSmallLabelTo(g, row + 0, 1, + helper.AddSmallLabelTo(g, row + 0, 1, content: $"# of {elemKindName}: " + idfIds.Count()); row += 1; @@ -4194,7 +4317,8 @@ public static async Task AssistantDeleteIdfsInRepo( // stat Action lambdaStat = (ok) => { - if (ok) { numOK++; } else { numNOK++; }; + if (ok) { numOK++; } else { numNOK++; } + ; ucProgTest.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + $"{numWrongType} were of wrong type,\n" + $"Id: {idf?.Id}"; @@ -4235,12 +4359,12 @@ public static async Task AssistantDeleteIdfsInRepo( // test if (idfExist.Count < 1) { - runtimeOptions?.Log.Info(StoredPrint.Color.Blue, + runtimeOptions?.Log.Info(StoredPrint.Color.Blue, $"No {elemKindName} to delete found. Finalizing! Location: {0}", recordJob.BaseAddress); return false; } - + // ask to proceed? if (AnyUiMessageBoxResult.Yes != displayContext.MessageBoxFlyoutShow( $"After checking individual ids, {idfExist.Count} {elemKindName}s seem to " + @@ -4299,7 +4423,8 @@ public static async Task AssistantDeleteIdfsInRepo( // stat Action lambdaStat = (ok) => { - if (ok) { numOK++; } else { numNOK++; }; + if (ok) { numOK++; } else { numNOK++; } + ; ucProgDel.Info = $"{numOK} entities exist, {numNOK} entities NOT found in {numTotal}\n" + $"Id: {idf?.Id}"; ucProgDel.Progress = 100.0 * (1.0 * (numOK + numNOK) / Math.Max(1, numTotal)); @@ -4333,6 +4458,358 @@ public static async Task AssistantDeleteIdfsInRepo( return true; } + + /// + /// Renames a range of Identifiables from an Repo / Registry. + /// Note: Currently, minimal interaction/ functionality is implemented. + /// + /// Each key to be an individual Identifiable! + /// Endpoint of renamed Identifiable, null else + public static async Task AssistantRenameIdfsInRepo( + Uri baseUri, + string oldId, + string newId, + PackCntRuntimeOptions runtimeOptions = null, + PackageContainerListBase containerList = null, + bool moreLog = false) + where T : Aas.IIdentifiable + { + // access + Uri newResUri = null; + if (baseUri == null || !baseUri.IsAbsoluteUri) + return null; + + // ok + try + { + // for all repo access, use the same client + var client = PackageHttpDownloadUtil.CreateHttpClient(baseUri, runtimeOptions, containerList: containerList); + + // + // Step 1 : Download the Identifiable from old Id + // + + var uri = BuildUriForRepoSingleIdentifiable(baseUri, oldId, encryptIds: true, usePost: false); + var idf = await PackageHttpDownloadUtil.DownloadIdentifiableToOK( + uri, runtimeOptions); + if (idf == null) + { + runtimeOptions?.Log?.Error( + "For renaming Identifiable, unable to download Identifiable. Skipping! Location: {0}", + uri.ToString()); + return null; + } + + if (moreLog) + Log.Singleton.Info("For renaming Identifiable, downloaded Identifiable with Id {0} from: {1}", + oldId, uri); + + // thumbnail + + byte[] aasThumbnail = null; + if (idf is Aas.IAssetAdministrationShell idfAas) + { + try + { + await PackageHttpDownloadUtil.HttpGetToMemoryStream( + client, + sourceUri: BuildUriForRepoAasThumbnail( + baseUri, idfAas.Id), + runtimeOptions: runtimeOptions, + lambdaDownloadDoneOrFail: (code, ms, contentFn) => + { + if (code != HttpStatusCode.OK) + return; + + try + { + aasThumbnail = ms.ToArray(); + + if (moreLog) + Log.Singleton.Info("For renaming Identifiable, downloaded AAS thumbnail for AAS {0}.", oldId); + } + catch (Exception ex) + { + runtimeOptions?.Log?.Error(ex, $"When trying to read thumbnail for Identifiable {oldId}"); + } + }); + } + catch (Exception ex) + { + if (moreLog) + Log.Singleton.Error(ex, $"When trying to read thumbnail for Identifiable {oldId}"); + } + } + + // + // Step 2 : Rename the Identifiable to new Id, upload + // + + idf.Id = newId; + + // the "working uri" will get the POST method + uri = BuildUriForRepoSingleIdentifiable(baseUri, newId, encryptIds: true, usePost: true); + + // the result endpoint is built as PUT, therefore having the full resource path in it + newResUri = BuildUriForRepoSingleIdentifiable(baseUri, newId, encryptIds: true, usePost: false); + + // Identifiable (should be post?!) + var stillOk = false; + + try + { + var res2 = await PackageHttpDownloadUtil.HttpPutPostIdentifiable( + client, + idf, + destUri: uri, + usePost: true, + runtimeOptions: runtimeOptions, + containerList: containerList); + + stillOk = true; + + if (moreLog) + Log.Singleton.Info("For renaming Identifiable, posted Identifiable with new Id {0} to " + + "new endpoint {1}.", newId, uri.AbsolutePath); + } + catch (Exception ex) + { + if (moreLog) + Log.Singleton.Error(ex, $"When trying to post Identifiable {newId}"); + } + + if (stillOk && aasThumbnail != null + && idf is Aas.IAssetAdministrationShell idfAas2) + { + try + { + using (var ms = new MemoryStream(aasThumbnail)) + { + // the multi-part content needs very specific information to work + var mpFn = Path.GetFileName(idfAas2.AssetInformation.DefaultThumbnail.Path); + var mpCt = idfAas2.AssetInformation.DefaultThumbnail.ContentType?.Trim(); + if (mpCt?.HasContent() != true) + mpCt = "application/octet-stream"; + + // where? + var attUri = BuildUriForRepoAasThumbnail(baseUri, newId, encryptIds: true); + + // do the PUT with multi-part content + // Note: according to the Swagger doc, this always is a PUT and never a POST !! + var res3 = await PackageHttpDownloadUtil.HttpPutPostFromMemoryStream( + null, // do NOT re-use client, as headers are re-defined! + ms, + destUri: attUri, + runtimeOptions: runtimeOptions, + containerList: containerList, + usePost: false /* usePost */, + useMultiPart: true, + mpParamName: "file", + mpFileName: mpFn, + mpContentType: mpCt); + + if (res3 != null && (int)res3.Item1 >= 200 && (int)res3.Item1 <= 299) + { + if (moreLog) + Log.Singleton.Info("For renaming Identifiable, uploaded AAS thumbnail for AAS {0}.", newId); + } + else + { + Log.Singleton.Error($"Error when trying to delete Identifiable {oldId}"); + } + } + } + catch (Exception ex) + { + if (moreLog) + Log.Singleton.Error(ex, $"When trying to post thumbnail of Identifiable {newId}"); + } + } + + // + // Step 3 : Delete old Id + // Assumption: potential thumbnail will be deleted automatically + // + + try + { + uri = BuildUriForRepoSingleIdentifiable(baseUri, oldId, encryptIds: true, usePost: false); + + var res = await PackageHttpDownloadUtil.HttpDeleteUri( + client, + delUri: uri, + runtimeOptions: runtimeOptions); + + if (res != null && (int)res.Item1 >= 200 && (int)res.Item1 <= 299) + { + if (moreLog) + Log.Singleton.Info("For renaming Identifiable, deleted Identifiable with old Id {0}.", oldId); + } + else + { + Log.Singleton.Error($"Error when trying to delete Identifiable {oldId}"); + } + } + catch (Exception ex) + { + if (moreLog) + Log.Singleton.Error(ex, $"When trying to delete Identifiable {oldId}"); + } + } + catch (Exception ex) + { + Log.Singleton.Error(ex, $"When renaming Identifiable {oldId} to {newId} in Repository {baseUri.ToString()}"); + return null; + } + + // return the new Identifiable URI + return newResUri; + } + + // + // Upload single Identifiable + // + + public class GetBaseAddressUploadRecord + { + /// + /// If set, will be used to display stats on the Identifiable. + /// + public Aas.IIdentifiable DisplayIdf; + + public string BaseAddress = ""; + public ConnectExtendedRecord.BaseTypeEnum BaseType = ConnectExtendedRecord.BaseTypeEnum.Repository; + + public bool Remember = false; + } + + /// + /// Performs the dialogue to get the base address for upload of a new Identifiable. + /// Presumes existing record to be filled with more information. + /// + public static async Task PerformGetBaseAddressUploadDialogue( + AasxMenuActionTicket ticket, + AnyUiContextBase displayContext, + string caption, + GetBaseAddressUploadRecord record) + { + // access + if (displayContext == null || caption?.HasContent() != true) + return false; + + // + // Screen + // + + 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[] { "200:", "*" }, + padding: new AnyUiThickness(0, 5, 0, 5), + margin: new AnyUiThickness(10, 0, 30, 0)); + + panel.Add(g); + + // dynamic rows + int row = 0; + + // usage Info + if (true) + { + helper.AddSmallInfoToRowPlus(g, ref row, + "An Identifiable seems to be new in the Environment and not in the Repository/ " + + "Registry. Please select base address for repo/ registry and proceed or cancel " + + "to skip the upload.", + column: 0, colSpan: 2); + + helper.AddSmallSeparatorToRowPlus(g, ref row); + } + + // Statistics + if (record.DisplayIdf != null) + { + helper.AddSmallLabelAndInfoToRowPlus(g, ref row, + "Identifiable:", record.DisplayIdf.GetType()?.Name) ; + + helper.AddSmallLabelAndInfoToRowPlus(g, ref row, + "IdShort:", record.DisplayIdf.IdShort); + + helper.AddSmallLabelAndInfoToRowPlus(g, ref row, + "Id:", record.DisplayIdf.Id); + + helper.AddSmallSeparatorToRowPlus(g, ref 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)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; }); + + if (displayContext is AnyUiContextPlusDialogs cpd + && cpd.HasCapability(AnyUiContextCapability.WPF)) + { + AnyUiUIElement.SetStringFromControl( + helper.Set( + helper.AddSmallComboBoxTo(g2, 0, 1, + isEditable: true, + items: Options.Curr.BaseAddresses? + .Concat(Options.Curr.KnownEndpoints?.Select((o) => o.BaseAddress)).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++; + + // remember? + helper.AddSmallLabelAndCheckboxToRowPlus(g, ref row, + contentLeft: "Remember selection:", + contentRight: "(If checked, selection will be positive for all following Identifiables.)", + isChecked: record.Remember, + setValue: (isChecked) => { record.Remember = isChecked; }); + + // give back + return g; + }); + + if (!(await displayContext.StartFlyoverModalAsync(uc))) + return false; + + return true; + } + } -} +} \ No newline at end of file diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs index e3547097a..51a8fa829 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs @@ -739,6 +739,10 @@ public static async Task DownloadIdentifiableToOK( PackCntRuntimeOptions runtimeOptions = null, bool allowFakeResponses = false) where T : Aas.IIdentifiable { + // access + if (location == null) + return default(T); + T res = default(T); await DownloadListOfIdentifiables( diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 7eccc5669..dd575d4de 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -1278,7 +1278,7 @@ public override void RefreshFromMainData() return new Tuple( veSmr.theAas, veSmr.theSubmodel, - theWrapper?.CollectIdShortByParent(separatorChar: '.', excludeIdentifiable: true)); + theWrapper?.CollectIdShortPathByParent(separatorChar: '.', excludeIdentifiable: true)); } // find just a Submodel @@ -1288,7 +1288,7 @@ public override void RefreshFromMainData() return new Tuple( null, veSm.theSubmodel, - theWrapper?.CollectIdShortByParent(separatorChar: '.', excludeIdentifiable: true)); + theWrapper?.CollectIdShortPathByParent(separatorChar: '.', excludeIdentifiable: true)); } return null; @@ -1446,8 +1446,12 @@ public override void RefreshFromMainData() { if (theCD != null) { + var td = ""; + if (GetTaintedTime() != null) + td = "\u273d "; + var ci = theCD.ToCaptionInfo(); - this.Caption = "" + ci.Item1 + " "; + this.Caption = td + "" + ci.Item1 + " "; this.Info = ci.Item2; // enrich? @@ -2115,6 +2119,7 @@ private VisualElementAdminShell GenerateVisuElemForAAS( var tiAsset = new VisualElementAsset(tiAas, cache, env, aas, asset); tiAas.IsExpanded = aasIsExpanded; tiAas.Members.Add(tiAsset); + tiAas.VirtualMembersAtTop++; } // have submodels? @@ -2337,7 +2342,7 @@ private void GenerateInnerElementsForConceptDescriptions( { // branch per Submodel var tiSM = new VisualElementEnvironmentItem( - parent: tiCDs, cache: cache, + parent: tiSubmodelsRoot, cache: cache, package: tiCDs.thePackage, env: tiCDs.theEnv, itemType: VisualElementEnvironmentItem.ItemType.Env); tiSM.Caption = "Submodel: " + sm.IdShort; @@ -3209,12 +3214,20 @@ private int UpdateByEventTryMoveGenericVE(PackCntChangeEventData data) int res = 0; // find the correct parent(s) - foreach (var parentVE in FindAllVisualElementOnMainDataObject( - data.ParentElem, alsoDereferenceObjects: true)) - { + // 25-08-21: do not use "ParentElem" but use "ThisElem" + // var thisVE = FindFirstVisualElementOnMainDataObject(data.ThisElem, alsoDereferenceObjects: true); + foreach (var thisVE in FindAllVisualElementOnMainDataObject(data.ThisElem, alsoDereferenceObjects: true)) + { + // exclude some (e.g. avoid AAS) + + // get the correct parent + var parentVE = thisVE?.Parent; + if (parentVE == null) + continue; + // trivial var ni = parentVE.VirtualMembersAtTop + data.NewIndex; - if (parentVE?.Members == null || ni < 0 || ni >= parentVE.Members.Count) + if (parentVE.Members == null || ni < 0 || ni >= parentVE.Members.Count) continue; // now, below these find direct childs matching the SME (only these can be removed) @@ -3233,6 +3246,7 @@ private int UpdateByEventTryMoveGenericVE(PackCntChangeEventData data) // moved res++; + break; } // ok @@ -3447,20 +3461,33 @@ public bool UpdateByEvent( if (data.Reason == PackCntChangeEventReason.MoveToIndex) { - if (data.ParentElem is Aas.IReferable parentMgr - && data.ThisElem is Aas.ISubmodelElement sme) + if (data.ParentElem is Aas.IReferable + && data.ThisElem is Aas.ISubmodelElement) { return 0 < UpdateByEventTryMoveGenericVE(data); } - if (data.ParentElem is Aas.AssetAdministrationShell aas - && data.ThisElem is Aas.Reference smref) + if (data.ParentElem is Aas.IAssetAdministrationShell + && data.ThisElem is Aas.IReference) { return 0 < UpdateByEventTryMoveGenericVE(data); } - if (data.ParentElem is List cds - && data.ThisElem is Aas.ConceptDescription cd) + if (data.ParentElem is Aas.IEnvironment + && data.ThisElem is Aas.IAssetAdministrationShell) + { + return 0 < UpdateByEventTryMoveGenericVE(data); + } + + if (data.ParentElem is Aas.IEnvironment + && data.ThisElem is Aas.ISubmodel) + { + return 0 < UpdateByEventTryMoveGenericVE(data); + } + + // if (data.ParentElem is List + if (data.ParentElem is Aas.IEnvironment + && data.ThisElem is Aas.IConceptDescription) { return 0 < UpdateByEventTryMoveGenericVE(data); } diff --git a/src/AasxPluginDocumentShelf/DocumentEntity.cs b/src/AasxPluginDocumentShelf/DocumentEntity.cs index 16513a646..8355db14d 100644 --- a/src/AasxPluginDocumentShelf/DocumentEntity.cs +++ b/src/AasxPluginDocumentShelf/DocumentEntity.cs @@ -85,7 +85,7 @@ public FileInfo(string aasId, string smId, Aas.File file) MimeType = file?.ContentType; AasId = aasId; SmId = smId; - IdShortPath = file?.CollectIdShortByParent(separatorChar: '.', excludeIdentifiable: true); + IdShortPath = file?.CollectIdShortPathByParent(separatorChar: '.', excludeIdentifiable: true); } } diff --git a/src/AasxPluginExportTable/Smt/AnyUiDialogueSmtExport.cs b/src/AasxPluginExportTable/Smt/AnyUiDialogueSmtExport.cs index 7e2f5a0c8..3b93c5135 100644 --- a/src/AasxPluginExportTable/Smt/AnyUiDialogueSmtExport.cs +++ b/src/AasxPluginExportTable/Smt/AnyUiDialogueSmtExport.cs @@ -226,9 +226,9 @@ public static async Task ExportSmtDialogBased( // export var export = new ExportSmt(); - export.ExportSmtToFile( + await export.ExportSmtToFile( log, displayContext, ticket.Package, - sm, pluginOptionsTable, record, fn); + ticket.AAS, sm, pluginOptionsTable, record, fn); // persist await displayContext.CheckIfDownloadAndStart(log, loc, fn); diff --git a/src/AasxPluginExportTable/Smt/ExportSmt.cs b/src/AasxPluginExportTable/Smt/ExportSmt.cs index 9b2fb6b41..f83cf29c3 100644 --- a/src/AasxPluginExportTable/Smt/ExportSmt.cs +++ b/src/AasxPluginExportTable/Smt/ExportSmt.cs @@ -98,14 +98,20 @@ protected string EvalLinkArguments(ExportSmtArguments args, Aas.ISubmodelElement return astr; } - protected void ProcessImageLink(Aas.ISubmodelElement sme) + protected async Task ProcessImageLink(Aas.ISubmodelElement sme, + string aasId = null, + string smId = null, + string idShortPath = null) { // first get to the data byte[] data = null; string dataExt = ".bin"; if (sme is Aas.IFile smeFile) { - data = _package?.GetBytesFromPackageOrExternal(smeFile.Value); + // old: data = _package?.GetBytesFromPackageOrExternal(smeFile.Value); + data = await _package?.GetBytesFromPackageOrExternalAsync( + smeFile.Value, aasId: aasId, smId: smId, idShortPath: idShortPath); + dataExt = Path.GetExtension(smeFile.Value); } @@ -332,10 +338,18 @@ protected void ProcessTables(Aas.IReferenceElement refel) } } - public void ExportSmtToFile( + protected class SmeLinearItem + { + public Aas.IReference SemId; + public Aas.ISubmodelElement Sme; + public List Parents; + } + + public async Task ExportSmtToFile( LogInstance log, AnyUiContextPlusDialogs displayContext, AdminShellPackageEnvBase package, + Aas.IAssetAdministrationShell aas, Aas.ISubmodel submodel, ExportTableOptions optionsAll, ExportSmtRecord optionsSmt, @@ -408,7 +422,8 @@ public void ExportSmtToFile( var defs = AasxPredefinedConcepts.AsciiDoc.Static; var mm = MatchMode.Relaxed; - // walk the Submodel + // walk the Submodel (need to linearize because of async) + var linearSmes = new List(); _srcSm.RecurseOnSubmodelElements(null, (o, parents, sme) => { // semantic id @@ -416,7 +431,23 @@ public void ExportSmtToFile( if (semId?.IsValid() != true) return true; - // elements + // add + linearSmes.Add(new SmeLinearItem() + { + SemId = semId, + Sme = sme, + Parents = parents.ToList() + }); + return true; + }); + + // now go this linear list + foreach (var item in linearSmes) + { + var semId = item.SemId; + var sme = item.Sme; + + // check for special semantic ids and process elements if (sme is Aas.IBlob blob) { if (semId.Matches(defs.CD_TextBlock.GetCdReference(), mm)) @@ -436,7 +467,10 @@ public void ExportSmtToFile( if (sme is Aas.IFile || sme is Aas.IBlob) { if (semId.Matches(defs.CD_ImageFile.GetCdReference(), mm)) - ProcessImageLink(sme); + await ProcessImageLink(sme, + aasId: aas?.Id, smId: submodel?.Id, + idShortPath: ExtendISubmodelElement.CollectIdShortPathBySmeAndParents( + sme, item.Parents, separatorChar: '.', excludeIdentifiable: true)); } if (sme is Aas.IReferenceElement refel) @@ -446,10 +480,7 @@ public void ExportSmtToFile( if (semId.Matches(defs.CD_GenerateTables.GetCdReference(), mm)) ProcessTables(refel); } - - // go further on - return true; - }); + } // ok, build raw Adoc var adocText = _adoc.ToString(); @@ -486,18 +517,6 @@ 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) { @@ -518,16 +537,36 @@ public void ExportSmtToFile( first = false; } #else - using (Package zip = System.IO.Packaging.Package.Open(fn, FileMode.Create)) + try + { + using (Package zip = System.IO.Packaging.Package.Open(fn, FileMode.Create)) + { + AdminShellUtil.RecursiveAddDirToZip( + zip, + _tempDir); + } + } catch (Exception ex) { - AdminShellUtil.RecursiveAddDirToZip( - zip, - _tempDir); + log?.Error(ex, $"ExportSmt: Error creating zip file {fn}"); + throw; } #endif log?.Info("ExportSmt: packed all files to {0}", fn); } + // now, view? + 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); + } + // remove temp directory Directory.Delete(_tempDir, recursive: true); log?.Info("ExportSmt: deleted temp directory."); diff --git a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs index 3a86ac5d6..3dc995aaa 100644 --- a/src/AasxPluginImageMap/ImageMapAnyUiControl.cs +++ b/src/AasxPluginImageMap/ImageMapAnyUiControl.cs @@ -389,7 +389,7 @@ protected void SetBasicInfos() if (fe?.Value != null) { // build path - var idShortPath = "" + fe.CollectIdShortByParent( + var idShortPath = "" + fe.CollectIdShortPathByParent( separatorChar: '.', excludeIdentifiable: true); // wrap async diff --git a/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs b/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs index 852dc0996..72ea32256 100644 --- a/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs +++ b/src/AasxPluginMtpViewer/WpfMtpControlWrapper.xaml.cs @@ -138,7 +138,7 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e) if (CheckIfPackageFile(inputFn)) { // build idShort Path - var idShortPath = "" + _activeMtpFileElem.CollectIdShortByParent( + var idShortPath = "" + _activeMtpFileElem.CollectIdShortPathByParent( separatorChar: '.', excludeIdentifiable: true); // _mtpTypeSm might be in another AAS diff --git a/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs b/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs index 9a60be0fa..8a2aecf43 100644 --- a/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs +++ b/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs @@ -7,18 +7,21 @@ 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 AasxPackageLogic; -using AasxPackageLogic.PackageCentral; -using AdminShellNS; -using AnyUi; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using AasxIntegrationBase; +using AasxPackageLogic; +using AasxPackageLogic.PackageCentral; +using AdminShellNS; +using AnyUi; +using static AasxPackageLogic.DispEditHelperBasics; using static AnyUi.AnyUiDisplayContextWpf; +using Aas = AasCore.Aas3_0; namespace AasxPackageExplorer { @@ -260,11 +263,24 @@ public void DisposeLoadedPlugin() } } + // + // 'Editing' from ouside + // + + public void AddDiaryStructuralChange(Aas.IReferable rf) + { + // access + if (rf == null || _helper == null) + return; + + _helper.AddDiaryEntry(rf, new DiaryEntryStructChange(), new DiaryReference(rf)); + } + // // Main function // - public DisplayRenderHints DisplayOrEditVisualAasxElement( + public async Task DisplayOrEditVisualAasxElement( PackageCentral packages, AnyUiDisplayContextWpf displayContext, ListOfVisualElementBasic entities, diff --git a/src/AnyUi/AnyUiSmallWidgetToolkit.cs b/src/AnyUi/AnyUiSmallWidgetToolkit.cs index f8e4a0729..62f06c8bf 100644 --- a/src/AnyUi/AnyUiSmallWidgetToolkit.cs +++ b/src/AnyUi/AnyUiSmallWidgetToolkit.cs @@ -576,6 +576,109 @@ public T Set(T fe, return fe; } + // + // Combinations of above + // + + public void AddSmallLabelAndTextToRowPlus( + AnyUiGrid grid, + ref int row, + string contentLabel, + string valueText, + Action setValue) + { + AddSmallLabelTo(grid, row, 0, content: contentLabel, + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AnyUiUIElement.SetStringFromControl( + Set( + AddSmallTextBoxTo(grid, row, 1, + text: valueText, + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + setValue); + + row++; + } + + public void AddSmallLabelAndCheckboxToRowPlus( + AnyUiGrid grid, + ref int row, + string contentLeft, + string contentRight, + bool isChecked, + Action setValue) + { + int col = 0; + if (contentLeft != null) + { + AddSmallLabelTo(grid, row, col++, content: contentLeft, + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + } + + var cb = AnyUiUIElement.SetBoolFromControl( + Set( + AddSmallCheckBoxTo(grid, row, col++, + content: contentRight, + isChecked: isChecked, + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center), + horizontalAlignment: AnyUiHorizontalAlignment.Stretch), + setValue); + + if (col < 2) + Set(cb, colSpan: 2); + + row++; + } + + public void AddSmallInfoToRowPlus( + AnyUiGrid grid, + ref int row, + string content, + int column = 1, + int colSpan = 1, + AnyUiTextWrapping wrapping = AnyUiTextWrapping.Wrap) + { + Set(AddSmallLabelTo(grid, row, column, content: content, + wrapping: wrapping), + colSpan: colSpan); + row++; + } + + public void AddSmallLabelAndInfoToRowPlus( + AnyUiGrid grid, + ref int row, + string contentLabel, + string contentInfo, + AnyUiTextWrapping wrapping = AnyUiTextWrapping.Wrap) + { + AddSmallLabelTo(grid, row, 0, content: contentLabel, + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center); + + AddSmallLabelTo(grid, row, 1, content: contentInfo, + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center, + wrapping: wrapping); + + row++; + } + + public void AddSmallSeparatorToRowPlus( + AnyUiGrid grid, + ref int row) + { + AddSmallBorderTo(grid, row, 0, + borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White, + colSpan: 2, + margin: new AnyUiThickness(0, 5, 0, 10)); + row++; + } + // // small widget handling // diff --git a/src/BlazorExplorer/Data/BlazorSession.CommandBindings.cs b/src/BlazorExplorer/Data/BlazorSession.CommandBindings.cs index f370ad60f..cc77bb1e3 100644 --- a/src/BlazorExplorer/Data/BlazorSession.CommandBindings.cs +++ b/src/BlazorExplorer/Data/BlazorSession.CommandBindings.cs @@ -98,7 +98,7 @@ public async Task CommandBinding_GeneralDispatch( this.CheckSmtMode = MainMenu?.IsChecked("CheckSmtElements") == true; // edit mode affects the total element view - RedrawAllAasxElements(nextFocusMdo: currMdo); + RedrawAllAasxElementsAsync(nextFocusMdo: currMdo); return; } diff --git a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs index a0d1d13af..6b422959b 100644 --- a/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs +++ b/src/BlazorExplorer/Data/BlazorSession.MainWindow.cs @@ -96,11 +96,13 @@ public void SaveScreenshot(string filename = "noname") // no such capability } - public void CommandExecution_RedrawAll() + public async Task CommandExecution_RedrawAllAsync() { + await Task.Yield(); + // redraw everything - RedrawAllAasxElements(); - RedrawElementView(); + await RedrawAllAasxElementsAsync(); + await RedrawElementViewAsync(); } private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() @@ -120,7 +122,7 @@ private PackCntRuntimeOptions UiBuildRuntimeOptionsForMainAppLoad() return ro; } - public void UiLoadPackageWithNew( + public async Task UiLoadPackageWithNew( PackageCentralItem packItem, AdminShellPackageEnvBase takeOverEnv = null, string loadLocalFilename = null, @@ -134,6 +136,8 @@ public void UiLoadPackageWithNew( bool? nextEditMode = null, bool autoFocusFirstRelevant = false) { + await Task.Yield(); + // access if (packItem == null) return; @@ -226,12 +230,12 @@ public bool CheckIsAnyTaintedIdentifiableInMain() return DisplayElements.IsAnyTaintedIdentifiable(); } - public void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null) + public async Task RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditMode = null) { if (onlyAuxiliary) { // reduced, in the background - RedrawAllAasxElements(); + await RedrawAllAasxElementsAsync(); } else { @@ -241,8 +245,8 @@ public void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditM // and -> this will update the left side of the screen correctly! // _mainMenu?.SetChecked("EditMenu", false); // ClearAllViews(); - RedrawAllAasxElements(); - RedrawElementView(); + await RedrawAllAasxElementsAsync(); + await RedrawElementViewAsync(); // ShowContentBrowser(Options.Curr.ContentHome, silent: true); // _eventHandling.Reset(); // dead-csharp on @@ -255,7 +259,7 @@ public void RestartUIafterNewPackage(bool onlyAuxiliary = false, bool? nextEditM /// Try remember which element was focussed and focus it after redrawing. /// Focus a new main data object attached to an tree element. /// If focussing, expand this item. - public void RedrawAllAasxElements( + public async Task RedrawAllAasxElementsAsync( bool keepFocus = false, object nextFocusMdo = null, bool wishExpanded = true) @@ -302,7 +306,7 @@ public void RedrawAllAasxElements( } // Info box .. - RedrawElementView(); + await RedrawElementViewAsync(); // display again Program.signalNewData( @@ -319,8 +323,10 @@ public void RedrawAllAasxElements( /// Based on save information, will redraw the AAS entity (element) view (right). /// /// Highlight field (for find/ replace) - public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightField = null) + public async Task RedrawElementViewAsync(DispEditHighlight.HighlightFieldInfo hightlightField = null) { + await Task.Yield(); + if (DisplayElements == null) return; @@ -346,7 +352,7 @@ public void ClearAllViews() DisplayElements.Clear(); } - public void DisplayElements_SelectedItemChanged(object sender, EventArgs e) + public async Task DisplayElements_SelectedItemChanged(object sender, EventArgs e) { // access if (DisplayElements == null || sender != DisplayElements) @@ -362,7 +368,7 @@ public void DisplayElements_SelectedItemChanged(object sender, EventArgs e) CheckIfToFlushEvents(); // redraw view - RedrawElementView(); + await RedrawElementViewAsync(); } /// @@ -604,7 +610,7 @@ public async Task UiHandleNavigateTo( // remember in history // ButtonHistory.Push(veFound); // fake selection - RedrawElementView(); + await RedrawElementViewAsync(); DisplayElements.Refresh(); // ContentTakeOver.IsEnabled = false; } diff --git a/src/BlazorExplorer/Data/BlazorSession.cs b/src/BlazorExplorer/Data/BlazorSession.cs index c6f5d0897..1bf1de69c 100644 --- a/src/BlazorExplorer/Data/BlazorSession.cs +++ b/src/BlazorExplorer/Data/BlazorSession.cs @@ -306,9 +306,36 @@ public BlazorSession() // nearly last task here .. Log.Singleton.Info("Application started .."); + // + // OLD code + // + + env = null; + + helper = new DispEditHelperMultiElement(); + helper.levelColors = DispLevelColors.GetLevelColorsFromOptions(Options.Curr); + + // some functionality still uses repo != null to detect editMode!! + repo = new ModifyRepo(); + helper.editMode = EditMode; + helper.hintMode = HintMode; + helper.repo = repo; + helper.context = null; + helper.packages = PackageCentral; + + ElementPanel = new AnyUiStackPanel() { Orientation = AnyUiOrientation.Vertical }; + + htmlDotnetThread = new Thread(AnyUiDisplayContextHtml.htmlDotnetLoop); + htmlDotnetThread.Start(); + + } + + // called once by the Index.razor component + public async Task InitializeAsync() { + // start with a new file PackageCentral.MainItem.New(); - RedrawAllAasxElements(); + await RedrawAllAasxElementsAsync(); // Try to load? if (Options.Curr.AasxToLoad != null) @@ -331,7 +358,7 @@ public BlazorSession() if (container == null) Log.Singleton.Error($"Failed to auto-load AASX from {location}"); else - UiLoadPackageWithNew(PackageCentral.MainItem, + await UiLoadPackageWithNew(PackageCentral.MainItem, takeOverContainer: container, onlyAuxiliary: false, indexItems: true); Log.Singleton.Info($"Successfully auto-loaded AASX {location}"); @@ -362,7 +389,7 @@ public BlazorSession() if (container == null) Log.Singleton.Error($"Failed to auto-load AASX from {location}"); else - UiLoadPackageWithNew(PackageCentral.AuxItem, + await UiLoadPackageWithNew(PackageCentral.AuxItem, takeOverContainer: container, onlyAuxiliary: true, indexItems: false); Log.Singleton.Info($"Successfully auto-loaded AASX {location}"); @@ -374,30 +401,13 @@ public BlazorSession() } // - // OLD + // Display right side // - env = null; - - helper = new DispEditHelperMultiElement(); - helper.levelColors = DispLevelColors.GetLevelColorsFromOptions(Options.Curr); - - // some functionality still uses repo != null to detect editMode!! - repo = new ModifyRepo(); - helper.editMode = EditMode; - helper.hintMode = HintMode; - helper.repo = repo; - helper.context = null; - helper.packages = PackageCentral; - - ElementPanel = new AnyUiStackPanel() { Orientation = AnyUiOrientation.Vertical }; - if (env?.AasEnv?.AssetAdministrationShells != null) helper.DisplayOrEditAasEntityAas(PackageCentral, env, env.AasEnv, env.AasEnv.AllAssetAdministrationShells().FirstOrDefault(), EditMode, ElementPanel, hintMode: HintMode); - htmlDotnetThread = new Thread(AnyUiDisplayContextHtml.htmlDotnetLoop); - htmlDotnetThread.Start(); } diff --git a/src/BlazorExplorer/Pages/Index.razor b/src/BlazorExplorer/Pages/Index.razor index f011c8ea9..bbb9f828d 100644 --- a/src/BlazorExplorer/Pages/Index.razor +++ b/src/BlazorExplorer/Pages/Index.razor @@ -967,7 +967,7 @@ if (ndm.ExecuteLambdaAction is AnyUiLambdaActionRedrawAllElementsBase larae) { // force the sticky update mode to false, because of total redraw - Session.RedrawAllAasxElements( + Session.RedrawAllAasxElementsAsync( keepFocus: true, nextFocusMdo: larae.NextFocus, wishExpanded: larae.IsExpanded ?? true); @@ -1004,7 +1004,7 @@ { // if an index is moved within the tree, both the tree shall be re-drawn // and the panel shall be updated (because of index) - Session.RedrawAllAasxElements(keepFocus: true); + Session.RedrawAllAasxElementsAsync(keepFocus: true); _onlyUpdatePluginUi = false; } @@ -1013,7 +1013,7 @@ // if an element is deleted within the tree, the tree shall be re-drawn, // the focus shall be shifted to a recommended element // and the panel shall be updated (because of index) - Session.RedrawAllAasxElements( + Session.RedrawAllAasxElementsAsync( keepFocus: true, nextFocusMdo: lapcc.NextFocus, wishExpanded: false); _onlyUpdatePluginUi = false; } @@ -1330,8 +1330,11 @@ private Timer _timer; - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { + // do the session initialization + await Session.InitializeAsync(); + // wire components FileDrop_Handler.FileDropped = async (ddof) => { diff --git a/src/BlazorExplorer/Startup.cs b/src/BlazorExplorer/Startup.cs index eb52e6ebe..407d3ebbb 100644 --- a/src/BlazorExplorer/Startup.cs +++ b/src/BlazorExplorer/Startup.cs @@ -47,7 +47,7 @@ public void ConfigureServices(IServiceCollection services) services.AddRazorPages(); services.AddServerSideBlazor(); services.AddSingleton(); - services.AddScoped(); + services.AddScoped(); // is only created once per session!! } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.