diff --git a/.github/workflows/BuildForWindows.yml b/.github/workflows/BuildForWindows.yml new file mode 100644 index 00000000..ea1ba210 --- /dev/null +++ b/.github/workflows/BuildForWindows.yml @@ -0,0 +1,91 @@ +name: Build and Release + +on: + push: + tags: + - "v*" +jobs: + build-and-release: + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.0.x + + - name: Add NuGet source + env: + NUGET_USERNAME: ${{ secrets.USER_NAME }} + NUGET_PASSWORD: ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }} + run: | + echo "" > nuget.config + echo "" >> nuget.config + echo '' >> nuget.config + echo '' >> nuget.config + echo "" >> nuget.config + echo "" >> nuget.config + echo "" >> nuget.config + echo '' >> nuget.config + echo '' >> nuget.config + echo "" >> nuget.config + echo "" >> nuget.config + echo "" >> nuget.config + + - name: Install dependencies + run: | + dotnet restore ./src/Asv.Drones.Gui.Api/Asv.Drones.Gui.Api.csproj + dotnet restore ./src/Asv.Drones.Gui/Asv.Drones.Gui.csproj + dotnet restore ./src/Asv.Drones.Gui.Desktop/Asv.Drones.Gui.Desktop.csproj + + - name: Build + run: | + dotnet build ./src/Asv.Drones.Gui.Api/Asv.Drones.Gui.Api.csproj --configuration Release --no-restore + dotnet build ./src/Asv.Drones.Gui/Asv.Drones.Gui.csproj --configuration Release --no-restore + dotnet build ./src/Asv.Drones.Gui.Desktop/Asv.Drones.Gui.Desktop.csproj --configuration Release --no-restore + + - name: Set version variable + env: + TAG: ${{ github.ref_name }} + run: echo "VERSION=${TAG#v}" >> $GITHUB_ENV + + + # here you must define path to your .csproj + - name: Publish project for installer + run: dotnet publish ./src/Asv.Drones.Gui.Desktop/Asv.Drones.Gui.Desktop.csproj -c Release -o ./publish/app + + - name: Install NSIS + run: | + choco install nsis + + #here you must define path to your .nsi file (it is used for installer setup and creation) + - name: Create EXE installer + run: makensis ./win-64-install.nsi + + - name: List output files + run: Get-ChildItem -Path ./publish/app -Force + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }} + RELEASE_BODY: ${{ steps.create-release-notes.outputs.release-notes }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: ${{ contains(github.ref, 'alpha') }} + + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./AsvDronesGuiInstaller.exe + asset_name: asv-drones-${{ github.ref_name }}-setup-windows-64.exe + asset_content_type: application/vnd.microsoft.portable-executable \ No newline at end of file diff --git a/.github/workflows/ReleaseDeployAction.yml b/.github/workflows/ReleaseDeployAction.yml deleted file mode 100644 index 906cce48..00000000 --- a/.github/workflows/ReleaseDeployAction.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Name of the workflow -name: Release - -# Run on every commit tag which begins with "v" (e.g., "v0.1.4") -on: - push: - tags: - - "v*" - -# Automatically create a GitHub Release, with release details specified (the relevant commits) -jobs: - release: - name: "Release" - runs-on: "windows-latest" - - steps: - - uses: "asv-soft/action-automatic-release@updated20" - with: - repo_token: "${{ secrets.GIHUB_NUGET_AUTH_TOKEN }}" - prerelease: false - files: | - *Setup.exe - *.zip diff --git a/src/.run/Win-x64.run.xml b/src/.run/Win-x64.run.xml index 2c32d50b..699e6f6d 100644 --- a/src/.run/Win-x64.run.xml +++ b/src/.run/Win-x64.run.xml @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/Asv.Drones.Gui.Api/Asv.Drones.Gui.Api.csproj b/src/Asv.Drones.Gui.Api/Asv.Drones.Gui.Api.csproj index cc25d583..3ca893e1 100644 --- a/src/Asv.Drones.Gui.Api/Asv.Drones.Gui.Api.csproj +++ b/src/Asv.Drones.Gui.Api/Asv.Drones.Gui.Api.csproj @@ -81,4 +81,9 @@ + + + ..\..\..\..\Users\Havok\.nuget\packages\asv.avalonia.toolkit\0.1.7\lib\net8.0\Asv.Avalonia.Toolkit.dll + + diff --git a/src/Asv.Drones.Gui.Api/CompatibilitySuppressions.xml b/src/Asv.Drones.Gui.Api/CompatibilitySuppressions.xml index 277a2ac9..e1f16b60 100644 --- a/src/Asv.Drones.Gui.Api/CompatibilitySuppressions.xml +++ b/src/Asv.Drones.Gui.Api/CompatibilitySuppressions.xml @@ -1,9 +1,16 @@  + + CP0002 + M:Asv.Drones.Gui.Api.IMavlinkDevicesService.GetRfsaByFullId(System.UInt16) + lib/net8.0/Asv.Drones.Gui.Api.dll + lib/net8.0/Asv.Drones.Gui.Api.dll + true + CP0006 - P:Asv.Drones.Gui.Api.ILocalizationService.Accuracy + M:Asv.Drones.Gui.Api.IMavlinkDevicesService.GetRfsaByFullId(System.UInt16) lib/net8.0/Asv.Drones.Gui.Api.dll lib/net8.0/Asv.Drones.Gui.Api.dll true diff --git a/src/Asv.Drones.Gui.Api/RS.Designer.cs b/src/Asv.Drones.Gui.Api/RS.Designer.cs index 6fe140e4..4c612d55 100644 --- a/src/Asv.Drones.Gui.Api/RS.Designer.cs +++ b/src/Asv.Drones.Gui.Api/RS.Designer.cs @@ -86,6 +86,15 @@ public static string Anchor_Editor_Action_Paste { } } + /// + /// Looks up a localized string similar to Actions. + /// + public static string AnchorsEditorView_TextBlock_Actions { + get { + return ResourceManager.GetString("AnchorsEditorView_TextBlock_Actions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Altitude. /// diff --git a/src/Asv.Drones.Gui.Api/RS.resx b/src/Asv.Drones.Gui.Api/RS.resx index c1d9ae68..add23c66 100644 --- a/src/Asv.Drones.Gui.Api/RS.resx +++ b/src/Asv.Drones.Gui.Api/RS.resx @@ -190,4 +190,7 @@ Paste + + Actions + \ No newline at end of file diff --git a/src/Asv.Drones.Gui.Api/RS.ru.resx b/src/Asv.Drones.Gui.Api/RS.ru.resx index 5c30337d..4f73ccca 100644 --- a/src/Asv.Drones.Gui.Api/RS.ru.resx +++ b/src/Asv.Drones.Gui.Api/RS.ru.resx @@ -186,4 +186,7 @@ Вставить + + Действия + \ No newline at end of file diff --git a/src/Asv.Drones.Gui.Api/Services/LogService/ILogService.cs b/src/Asv.Drones.Gui.Api/Services/LogService/ILogService.cs index e66d11ea..29aace0d 100644 --- a/src/Asv.Drones.Gui.Api/Services/LogService/ILogService.cs +++ b/src/Asv.Drones.Gui.Api/Services/LogService/ILogService.cs @@ -1,5 +1,16 @@ +using ReactiveUI; + namespace Asv.Drones.Gui.Api; +public static class LogHelper +{ + public static IDisposable CatchToLog(this ReactiveCommand cmd, ILogService log, string sender) + { + return cmd.ThrownExceptions.Subscribe(ex=>log.Error(sender, ex.Message,ex)); + } +} + + public interface ILogService { IObservable OnMessage { get; } @@ -7,14 +18,19 @@ public interface ILogService IEnumerable LoadItemsFromLogFile(); void DeleteLogFile(); + public IDisposable CatchToLog( ReactiveCommand cmd, string sender) + { + return cmd.ThrownExceptions.Subscribe(ex=>Error(sender, ex.Message,ex)); + } + public void Fatal(string sender, string message, - Exception ex = default) + Exception? ex = default) { SaveMessage(new LogMessage(DateTime.Now, LogMessageType.Fatal, sender, message, ex?.Message)); } public void Error(string sender, string message, - Exception ex = default) + Exception? ex = default) { SaveMessage(new LogMessage(DateTime.Now, LogMessageType.Error, sender, message, ex?.Message)); } diff --git a/src/Asv.Drones.Gui.Api/Services/Mavlink/IMavlinkDevicesService.cs b/src/Asv.Drones.Gui.Api/Services/Mavlink/IMavlinkDevicesService.cs index f5a0ce84..3298fb13 100644 --- a/src/Asv.Drones.Gui.Api/Services/Mavlink/IMavlinkDevicesService.cs +++ b/src/Asv.Drones.Gui.Api/Services/Mavlink/IMavlinkDevicesService.cs @@ -54,12 +54,13 @@ public interface IMavlinkDevicesService /// Id of searched vehicle /// Vehicle object IVehicleClient? GetVehicleByFullId(ushort id); - IObservable> BaseStations { get; } IGbsClientDevice? GetGbsByFullId(ushort id); IObservable> Payloads { get; } ISdrClientDevice? GetPayloadsByFullId(ushort id); IObservable> AdsbDevices { get; } IAdsbClientDevice? GetAdsbVehicleByFullId(ushort id); + IObservable> RfsaDevices { get; } + IRfsaClientDevice? GetRfsaByFullId(ushort id); } } \ No newline at end of file diff --git a/src/Asv.Drones.Gui.Api/Services/Mavlink/MavlinkHelper.cs b/src/Asv.Drones.Gui.Api/Services/Mavlink/MavlinkHelper.cs index 419266a1..fff284de 100644 --- a/src/Asv.Drones.Gui.Api/Services/Mavlink/MavlinkHelper.cs +++ b/src/Asv.Drones.Gui.Api/Services/Mavlink/MavlinkHelper.cs @@ -54,7 +54,8 @@ public static MaterialIconKind GetIcon(DeviceClass type) DeviceClass.SdrPayload => MaterialIconKind.Radio, DeviceClass.GbsRtk => MaterialIconKind.RouterWireless, DeviceClass.Adsb => MaterialIconKind.Radar, - _ => MaterialIconKind.Navigation, + DeviceClass.Rfsa => MaterialIconKind.Waveform, + _ => MaterialIconKind.HelpNetworkOutline, }; } } diff --git a/src/Asv.Drones.Gui.Api/Services/Plugins/ILocalPluginInfo.cs b/src/Asv.Drones.Gui.Api/Services/Plugins/ILocalPluginInfo.cs index a879b434..30bd2e87 100644 --- a/src/Asv.Drones.Gui.Api/Services/Plugins/ILocalPluginInfo.cs +++ b/src/Asv.Drones.Gui.Api/Services/Plugins/ILocalPluginInfo.cs @@ -1,3 +1,5 @@ +using Asv.Common; + namespace Asv.Drones.Gui.Api; public interface ILocalPluginInfo : IPluginSpecification @@ -21,6 +23,7 @@ public interface IPluginSearchInfo : IPluginSpecification public interface IPluginSpecification { + SemVersion ApiVersion { get; } string PackageId { get; } string? Title { get; } public string? Description { get; } diff --git a/src/Asv.Drones.Gui.Api/Services/Plugins/IPluginManager.cs b/src/Asv.Drones.Gui.Api/Services/Plugins/IPluginManager.cs index 7dc6a6b9..a4a2f377 100644 --- a/src/Asv.Drones.Gui.Api/Services/Plugins/IPluginManager.cs +++ b/src/Asv.Drones.Gui.Api/Services/Plugins/IPluginManager.cs @@ -1,3 +1,4 @@ +using Asv.Common; using DynamicData; namespace Asv.Drones.Gui.Api; @@ -31,6 +32,7 @@ Task Install(IPluginServerInfo source, string packageId, string version, IProgre void CancelUninstall(ILocalPluginInfo pluginInfo); IEnumerable Installed { get; } bool IsInstalled(string packageId, out ILocalPluginInfo? info); + SemVersion ApiVersion { get; } } public class SearchQuery diff --git a/src/Asv.Drones.Gui.Api/Services/Plugins/NullPluginManager.cs b/src/Asv.Drones.Gui.Api/Services/Plugins/NullPluginManager.cs index e9ac553b..a8b3875a 100644 --- a/src/Asv.Drones.Gui.Api/Services/Plugins/NullPluginManager.cs +++ b/src/Asv.Drones.Gui.Api/Services/Plugins/NullPluginManager.cs @@ -1,4 +1,6 @@ -namespace Asv.Drones.Gui.Api; +using Asv.Common; + +namespace Asv.Drones.Gui.Api; public class NullPluginManager : IPluginManager { @@ -43,4 +45,6 @@ public bool IsInstalled(string packageId, out ILocalPluginInfo? info) info = null; return false; } + + public SemVersion ApiVersion { get; } = new(0); } \ No newline at end of file diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreEntryViewModel.cs b/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreEntryViewModel.cs index 88860e84..07976470 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreEntryViewModel.cs +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreEntryViewModel.cs @@ -78,7 +78,7 @@ public HierarchicalStoreEntryViewModel() [Reactive] public string Description { get; set; } - protected virtual void OnError(HierarchicalStoreEntryAction action, Exception exception) + protected virtual void OnError(HierarchicalStoreEntryAction action, Exception? exception) { } @@ -161,7 +161,7 @@ public HierarchicalStoreEntryViewModel(Node, TKey> ParentId = node.Item.ParentId; } - protected override void OnError(HierarchicalStoreEntryAction action, Exception ex) + protected override void OnError(HierarchicalStoreEntryAction action, Exception? ex) { base.OnError(action, ex); _log.Error("Store", $"Error to '{action}' entry", ex); diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreView.axaml b/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreView.axaml index 528127c4..0578b0c0 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreView.axaml +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreView.axaml @@ -74,24 +74,27 @@ Text="{Binding SearchText}" /> - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreViewModel.cs b/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreViewModel.cs index 20b077e5..0029507f 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreViewModel.cs +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/HierarchicalStore/HierarchicalStoreViewModel.cs @@ -172,7 +172,7 @@ protected virtual void CreateNewFolderImpl() { } - protected virtual void OnError(HierarchicalStoreAction action, Exception ex) + protected virtual void OnError(HierarchicalStoreAction action, Exception? ex) { } @@ -236,7 +236,7 @@ public HierarchicalStoreViewModel(Uri id, IHierarchicalStore store, .Subscribe(x => x?.Refresh()).DisposeItWith(Disposable); } - protected override void OnError(HierarchicalStoreAction action, Exception ex) + protected override void OnError(HierarchicalStoreAction action, Exception? ex) { _log.Error(DisplayName, $"Error to '{action:G}'", ex); } diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/Map/Actions/Ruler/MapRulerActionViewModel.cs b/src/Asv.Drones.Gui.Api/Tools/Controls/Map/Actions/Ruler/MapRulerActionViewModel.cs index e02afc62..88c2faf7 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/Map/Actions/Ruler/MapRulerActionViewModel.cs +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/Map/Actions/Ruler/MapRulerActionViewModel.cs @@ -121,7 +121,6 @@ public RulerStartAnchor(Uri id, GeoPoint startPoint) : base(id) OffsetX = OffsetXEnum.Center; OffsetY = OffsetYEnum.Bottom; StrokeThickness = 1; - BaseStrokeThickness = 1; IconBrush = Brushes.Indigo; Stroke = Brushes.White; IsVisible = true; @@ -139,8 +138,7 @@ public RulerStopAnchor(Uri id, RulerStartAnchor start, ILocalizationService loc, BaseSize = 48; OffsetX = OffsetXEnum.Center; OffsetY = OffsetYEnum.Bottom; - StrokeThickness = 1; - BaseStrokeThickness = 1; + StrokeThickness = 1; IconBrush = Brushes.Indigo; Stroke = Brushes.White; IsVisible = true; diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/Map/MapPageView.axaml b/src/Asv.Drones.Gui.Api/Tools/Controls/Map/MapPageView.axaml index 9e44dcfb..71af4022 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/Map/MapPageView.axaml +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/Map/MapPageView.axaml @@ -63,7 +63,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -81,8 +81,8 @@ - + CornerRadius="{DynamicResource ControlCornerRadius}" IsVisible="{Binding !!MapActions.Count}"> - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0,0,0,0 - 0,0,0,0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/Map/Widgets/AnchorEditor/AnchorsEditorViewModel.cs b/src/Asv.Drones.Gui.Api/Tools/Controls/Map/Widgets/AnchorEditor/AnchorsEditorViewModel.cs index 8f79ea2d..c8e24c76 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/Map/Widgets/AnchorEditor/AnchorsEditorViewModel.cs +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/Map/Widgets/AnchorEditor/AnchorsEditorViewModel.cs @@ -249,6 +249,7 @@ protected override void InternalAfterMapInit(IMap context) context.WhenValueChanged(_ => _.SelectedItem) .Subscribe(_ => { + Actions = null; if (_prevAnchor != null && _prevAnchor.IsInEditMode) { UpdateLatitude(Latitude); @@ -261,7 +262,7 @@ protected override void InternalAfterMapInit(IMap context) if (_ != null) { - Actions = _.Actions; + if (_.Actions is not null) Actions = _.Actions.OrderByDescending(_=>_.Title.Length); _prevAnchor = _; IsVisible = true; IsEditable = _.IsEditable; diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/Params/ParamItemViewModel.cs b/src/Asv.Drones.Gui.Api/Tools/Controls/Params/ParamItemViewModel.cs index edfd87da..54b28b74 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/Params/ParamItemViewModel.cs +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/Params/ParamItemViewModel.cs @@ -193,12 +193,12 @@ public ParamItemViewModel(Uri id, IParamItem paramItem, ILogService log) : base( .DisposeItWith(Disposable); } - private void OnWriteError(Exception ex) + private void OnWriteError(Exception? ex) { _log.Error("Params", $"Write {Name} error", ex); } - private void OnReadError(Exception ex) + private void OnReadError(Exception? ex) { _log.Error("Params", $"Read {Name} error", ex); } diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/Params/ParamPageViewModel.cs b/src/Asv.Drones.Gui.Api/Tools/Controls/Params/ParamPageViewModel.cs index 752a77ee..65074ac6 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/Params/ParamPageViewModel.cs +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/Params/ParamPageViewModel.cs @@ -251,7 +251,7 @@ public override async Task TryClose() return true; } - private void OnRefreshError(Exception ex) + private void OnRefreshError(Exception? ex) { _log.Error("Params view", "Error to read all params items", ex); } diff --git a/src/Asv.Drones.Gui.Api/Tools/Controls/TreePage/TreePageExplorerViewModel.cs b/src/Asv.Drones.Gui.Api/Tools/Controls/TreePage/TreePageExplorerViewModel.cs index 4d0ffa74..021aae92 100644 --- a/src/Asv.Drones.Gui.Api/Tools/Controls/TreePage/TreePageExplorerViewModel.cs +++ b/src/Asv.Drones.Gui.Api/Tools/Controls/TreePage/TreePageExplorerViewModel.cs @@ -158,7 +158,7 @@ private async Task Navigate(TreePartMenuItemContainer? menu) menu.IsSelected = true; return true; } - catch (Exception e) + catch (Exception? e) { _log.Error(Title, $"Can't create page {menu.Base.Name}:{e.Message}", e); return false; diff --git a/src/Asv.Drones.Gui.Api/WellKnownUri.cs b/src/Asv.Drones.Gui.Api/WellKnownUri.cs index 8bb51d87..b1854395 100644 --- a/src/Asv.Drones.Gui.Api/WellKnownUri.cs +++ b/src/Asv.Drones.Gui.Api/WellKnownUri.cs @@ -76,7 +76,9 @@ public static class WellKnownUri public const string ShellPageMapPlaningActionRuler = $"{ShellPageMapPlaningAction}.ruler"; public const string ShellPageMapPlaningActionMover = $"{ShellPageMapPlaningAction}.mover"; public const string ShellPageMapPlaningMissionBrowser = $"{ShellPageMapPlaning}.browser"; + public const string ShellPageMapPlaningMissionSavingBrowser = $"{ShellPageMapPlaning}.browser"; public static readonly Uri ShellPageMapPlaningMissionBrowserUri = new(ShellPageMapPlaningMissionBrowser); + public static readonly Uri ShellPageMapPlaningMissionSavingBrowserUri = new(ShellPageMapPlaningMissionSavingBrowser); public const string ShellPageMapPlaningWidget = $"{ShellPageMapPlaning}.widget"; public const string ShellPageMapPlaningWidgetAnchorEditor = $"{ShellPageMapPlaningWidget}.editor"; public const string ShellPageMapPlaningWidgetEditor = $"{ShellPageMapPlaningWidget}.mission-editor"; diff --git a/src/Asv.Drones.Gui/Asv.Drones.Gui.csproj b/src/Asv.Drones.Gui/Asv.Drones.Gui.csproj index bb03e1c4..7f98bd5d 100644 --- a/src/Asv.Drones.Gui/Asv.Drones.Gui.csproj +++ b/src/Asv.Drones.Gui/Asv.Drones.Gui.csproj @@ -26,7 +26,6 @@ - @@ -53,14 +52,15 @@ PublicResXFileCodeGenerator RS.Designer.cs - - - True True RS.resx + + + + MainWindow.axaml Code @@ -93,6 +93,14 @@ ArduCopterQuickParamStandardTreePageView.axaml Code + + PlaningMissionSavingBrowserView.axaml + Code + + + PlaningMissionSavingBrowserView.axaml + Code + @@ -103,4 +111,22 @@ + + + + Never + + + + + + ..\..\..\..\Users\Havok\.nuget\packages\asv.avalonia.toolkit\0.1.7\lib\net8.0\Asv.Avalonia.Toolkit.dll + + + + + + ..\..\..\..\Users\Havok\.nuget\packages\asv.avalonia.toolkit\0.1.7\lib\net8.0\Asv.Avalonia.Toolkit.dll + + diff --git a/src/Asv.Drones.Gui/RS.Designer.cs b/src/Asv.Drones.Gui/RS.Designer.cs index 67dc91a5..a4b02864 100644 --- a/src/Asv.Drones.Gui/RS.Designer.cs +++ b/src/Asv.Drones.Gui/RS.Designer.cs @@ -1490,6 +1490,15 @@ public static string HeaderPlaningFileOpenMenuItem_Title { } } + /// + /// Looks up a localized string similar to Save As.... + /// + public static string HeaderPlaningFileSaveAsMenuItem_Title { + get { + return ResourceManager.GetString("HeaderPlaningFileSaveAsMenuItem_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Save. /// @@ -3434,6 +3443,61 @@ public static string PlanningPageViewModel_MissionDownloaded { } } + /// + /// Looks up a localized string similar to A file with the same name already exists. After performing this action, overwriting will occur. Are you sure you want to continue?. + /// + public static string PlanningPageViewModel_MissionSavingBrowserDialog_ConfirmationText_ContentText { + get { + return ResourceManager.GetString("PlanningPageViewModel_MissionSavingBrowserDialog_ConfirmationText_ContentText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rewrite. + /// + public static string PlanningPageViewModel_MissionSavingBrowserDialog_ConfirmationText_PrimaryButtonText { + get { + return ResourceManager.GetString("PlanningPageViewModel_MissionSavingBrowserDialog_ConfirmationText_PrimaryButtonTe" + + "xt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Confirmation. + /// + public static string PlanningPageViewModel_MissionSavingBrowserDialog_ConfirmationText_Title { + get { + return ResourceManager.GetString("PlanningPageViewModel_MissionSavingBrowserDialog_ConfirmationText_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save. + /// + public static string PlanningPageViewModel_MissionSavingBrowserDialog_PrimaryButton { + get { + return ResourceManager.GetString("PlanningPageViewModel_MissionSavingBrowserDialog_PrimaryButton", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cancel. + /// + public static string PlanningPageViewModel_MissionSavingBrowserDialog_SecondaryButton { + get { + return ResourceManager.GetString("PlanningPageViewModel_MissionSavingBrowserDialog_SecondaryButton", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Chose the file name to save. + /// + public static string PlanningPageViewModel_MissionSavingBrowserDialog_Title { + get { + return ResourceManager.GetString("PlanningPageViewModel_MissionSavingBrowserDialog_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Mission uploaded!. /// @@ -3957,7 +4021,7 @@ public static string RebootAutopilotAnchorActionViewModel_LogMessage { } /// - /// Looks up a localized string similar to Reboot/Shutdown Autopilot. + /// Looks up a localized string similar to Reboot/Shutdown. /// public static string RebootAutopilotAnchorActionViewModel_Title { get { diff --git a/src/Asv.Drones.Gui/RS.resx b/src/Asv.Drones.Gui/RS.resx index 7434c1e9..605ffead 100644 --- a/src/Asv.Drones.Gui/RS.resx +++ b/src/Asv.Drones.Gui/RS.resx @@ -1190,8 +1190,8 @@ Choose companion reboot/shutdown mode - - Reboot/Shutdown Autopilot + + Reboot/Shutdown Ok @@ -1847,4 +1847,25 @@ Accuracy + + Save As... + + + Save + + + Cancel + + + Chose the file name to save + + + Confirmation + + + Rewrite + + + A file with the same name already exists. After performing this action, overwriting will occur. Are you sure you want to continue? + \ No newline at end of file diff --git a/src/Asv.Drones.Gui/RS.ru.resx b/src/Asv.Drones.Gui/RS.ru.resx index 7d9e158f..a2588ed6 100644 --- a/src/Asv.Drones.Gui/RS.ru.resx +++ b/src/Asv.Drones.Gui/RS.ru.resx @@ -1219,8 +1219,8 @@ Выберите режим перезапуска/отключения бортового компьютера - - Перезагрузить/Выключить автопилот + + Перезагрузить/Выключить Ок @@ -1231,8 +1231,8 @@ Пользователь отправил комманду перезагрузки автопилота с параметрами {0} и {1} - - Выбор режима БПЛА + + Режимы БПЛА Ок @@ -1393,8 +1393,8 @@ Настройки - - Немедленно приземлиться + + Приземлиться Перейти в точку @@ -1408,8 +1408,8 @@ Пользователь отправил команду приземлиться для {0} - - Вернуться в точку запуска + + Вернуться домой Пользователь отправил команду вернуться в точку запуска для {0} @@ -1811,7 +1811,7 @@ мин - Вернуться в точку запуска + Вернуться домой Начать миссию @@ -1879,4 +1879,25 @@ Точность + + Сохранить как... + + + Сохранить + + + Отменить + + + Выберите имя файла для сохранения + + + Подтверждение + + + Перезаписать + + + Файл с таким именем уже существует. После выполнения данного действия произойдёт перезапись. Вы уверены, что хотите продолжить? + \ No newline at end of file diff --git a/src/Asv.Drones.Gui/Services/LogService/LogService.cs b/src/Asv.Drones.Gui/Services/LogService/LogService.cs index b14eb9fe..a99f37da 100644 --- a/src/Asv.Drones.Gui/Services/LogService/LogService.cs +++ b/src/Asv.Drones.Gui/Services/LogService/LogService.cs @@ -39,9 +39,15 @@ public LogService(string logsFolder) Layout.FromString("${longdate}|${threadid}|${level}|${logger}|${message} ${exception:format=tostring}"); #if DEBUG builder.ForLogger().FilterMinLevel(LogLevel.Trace).WriteToDebug(layout: layout); + builder.ForLogger().FilterLevels(LogLevel.Trace, LogLevel.Fatal).WriteToFile( + fileName: _logFile, + lineEnding: LineEndingMode.CRLF, + maxArchiveDays: 30, + maxArchiveFiles: 30, + layout: layout); #endif builder.ForLogger().FilterMinLevel(LogLevel.Trace).WriteToColoredConsole(layout: layout); - builder.ForLogger().FilterMinLevel(LogLevel.Trace).WriteToFile( + builder.ForLogger().FilterLevels(LogLevel.Trace, LogLevel.Fatal).WriteToFile( fileName: _logFile, lineEnding: LineEndingMode.CRLF, maxArchiveDays: 30, @@ -71,6 +77,7 @@ public LogService(string logsFolder) public void SaveMessage(LogMessage logMessage) { _onMessage.OnNext(logMessage); + Logger.Log(LogLevel.FromString(logMessage.Type.ToString()), logMessage.Message ); } public void DeleteLogFile() diff --git a/src/Asv.Drones.Gui/Services/Mavlink/MavlinkDevicesService.cs b/src/Asv.Drones.Gui/Services/Mavlink/MavlinkDevicesService.cs index 16220dcd..302368bf 100644 --- a/src/Asv.Drones.Gui/Services/Mavlink/MavlinkDevicesService.cs +++ b/src/Asv.Drones.Gui/Services/Mavlink/MavlinkDevicesService.cs @@ -33,6 +33,7 @@ public class MavlinkDeviceServiceConfig public SdrClientDeviceConfig Sdr { get; set; } = new(); public AdsbClientDeviceConfig Adsb { get; set; } = new(); public bool WrapToV2ExtensionEnabled { get; set; } = true; + public RfsaClientDeviceConfig Rfsa { get; set; } = new(); } [Export(typeof(IMavlinkDevicesService))] @@ -61,8 +62,8 @@ public MavlinkDevicesService(IConfiguration config, IPacketSequenceCalculator se _mavlinkRouter = new MavlinkRouter(MavlinkV2Connection.RegisterDefaultDialects, publishScheduler: RxApp.MainThreadScheduler).DisposeItWith(Disposable); - _mavlinkRouter.WrapToV2ExtensionEnabled = InternalGetConfig(_ => _.WrapToV2ExtensionEnabled); - foreach (var port in InternalGetConfig(_ => _.Ports)) + _mavlinkRouter.WrapToV2ExtensionEnabled = InternalGetConfig(s => s.WrapToV2ExtensionEnabled); + foreach (var port in InternalGetConfig(s => s.Ports)) { _mavlinkRouter.AddPort(port); } @@ -76,20 +77,20 @@ public MavlinkDevicesService(IConfiguration config, IPacketSequenceCalculator se #region InitUriHost mavlink heartbeat - var serverIdentity = InternalGetConfig(_ => new MavlinkIdentity(_.SystemId, _.ComponentId)); - var serverConfig = InternalGetConfig(_ => new ServerDeviceConfig - { Heartbeat = new MavlinkHeartbeatServerConfig { HeartbeatRateMs = _.HeartbeatRateMs } }); + var serverIdentity = InternalGetConfig(s => new MavlinkIdentity(s.SystemId, s.ComponentId)); + var serverConfig = InternalGetConfig(s => new ServerDeviceConfig + { Heartbeat = new MavlinkHeartbeatServerConfig { HeartbeatRateMs = s.HeartbeatRateMs } }); var serverDevice = new ServerDevice(Router, sequenceCalculator, serverIdentity, serverConfig, Scheduler.Default) .DisposeItWith(Disposable); - serverDevice.Heartbeat.Set(_ => + serverDevice.Heartbeat.Set(p => { - _.Autopilot = MavAutopilot.MavAutopilotInvalid; - _.BaseMode = 0; - _.CustomMode = 0; - _.MavlinkVersion = 3; - _.SystemStatus = MavState.MavStateActive; - _.Type = MavType.MavTypeGcs; + p.Autopilot = MavAutopilot.MavAutopilotInvalid; + p.BaseMode = 0; + p.CustomMode = 0; + p.MavlinkVersion = 3; + p.SystemStatus = MavState.MavStateActive; + p.Type = MavType.MavTypeGcs; }); _systemId = new RxValue(serverIdentity.SystemId).DisposeItWith(Disposable); _systemId @@ -97,7 +98,7 @@ public MavlinkDevicesService(IConfiguration config, IPacketSequenceCalculator se .DistinctUntilChanged() .Skip(1) .Do(_ => _needReloadToApplyConfig.Value = true) - .Subscribe(_ => InternalSaveConfig(cfg => cfg.SystemId = _)) + .Subscribe(b => InternalSaveConfig(cfg => cfg.SystemId = b)) .DisposeItWith(Disposable); _componentId = new RxValue(serverIdentity.ComponentId).DisposeItWith(Disposable); _componentId @@ -105,16 +106,16 @@ public MavlinkDevicesService(IConfiguration config, IPacketSequenceCalculator se .DistinctUntilChanged() .Skip(1) .Do(_ => _needReloadToApplyConfig.Value = true) - .Subscribe(_ => InternalSaveConfig(cfg => cfg.ComponentId = _)) + .Subscribe(b => InternalSaveConfig(cfg => cfg.ComponentId = b)) .DisposeItWith(Disposable); - var heartbeatRateMs = InternalGetConfig(_ => TimeSpan.FromMilliseconds(_.HeartbeatRateMs)); + var heartbeatRateMs = InternalGetConfig(s => TimeSpan.FromMilliseconds(s.HeartbeatRateMs)); _heartBeatRate = new RxValue(heartbeatRateMs).DisposeItWith(Disposable); _heartBeatRate .Throttle(TimeSpan.FromSeconds(1)) .DistinctUntilChanged() .Skip(1) .Do(_ => _needReloadToApplyConfig.Value = true) - .Subscribe(_ => { InternalSaveConfig(cfg => cfg.HeartbeatRateMs = (int)_.TotalMilliseconds); }) + .Subscribe(s => { InternalSaveConfig(cfg => cfg.HeartbeatRateMs = (int)s.TotalMilliseconds); }) .DisposeItWith(Disposable); serverDevice.Heartbeat.Start(); @@ -122,14 +123,14 @@ public MavlinkDevicesService(IConfiguration config, IPacketSequenceCalculator se #region Mavlink devices - var deviceTimeout = InternalGetConfig(_ => TimeSpan.FromMilliseconds(_.DeviceHeartbeatTimeoutMs)); + var deviceTimeout = InternalGetConfig(s => TimeSpan.FromMilliseconds(s.DeviceHeartbeatTimeoutMs)); _deviceBrowser = new MavlinkDeviceBrowser(_mavlinkRouter, deviceTimeout, RxApp.MainThreadScheduler) .DisposeItWith(Disposable); _deviceBrowser.DeviceTimeout .Throttle(TimeSpan.FromSeconds(1)) .DistinctUntilChanged() .Skip(1) - .Subscribe(_ => InternalSaveConfig(cfg => cfg.DeviceHeartbeatTimeoutMs = (int)_.TotalMilliseconds)) + .Subscribe(s => InternalSaveConfig(cfg => cfg.DeviceHeartbeatTimeoutMs = (int)s.TotalMilliseconds)) .DisposeItWith(Disposable); #endregion @@ -138,29 +139,35 @@ public MavlinkDevicesService(IConfiguration config, IPacketSequenceCalculator se Vehicles = Devices .Transform(CreateVehicle) - .Filter(_ => _ != null) + .Filter(c => c != null) .DisposeMany() .RefCount(); BaseStations = Devices - .Filter(_ => _.Type == (MavType)Mavlink.V2.AsvGbs.MavType.MavTypeAsvGbs) + .Filter(d => d.Type == (MavType)Mavlink.V2.AsvGbs.MavType.MavTypeAsvGbs) .Transform(CreateBaseStation) .DisposeMany() .RefCount(); Payloads = Devices - .Filter(_ => _.Type == (MavType)Mavlink.V2.AsvSdr.MavType.MavTypeAsvSdrPayload) + .Filter(d => d.Type == (MavType)Mavlink.V2.AsvSdr.MavType.MavTypeAsvSdrPayload) .Transform(CreateSdrDevice) .DisposeMany() .RefCount(); AdsbDevices = Devices - .Filter(_ => _.Type == MavType.MavTypeAdsb) + .Filter(d => d.Type == MavType.MavTypeAdsb) .Transform(CreateAdsbDevice) .DisposeMany() .RefCount(); + RfsaDevices = Devices + .Filter(d => d.Type == (MavType)Mavlink.V2.AsvRfsa.MavType.MavTypeAsvRfsa) + .Transform(CreateRfsaDevice) + .DisposeMany() + .RefCount(); AllDevices = Vehicles.Transform(x => (IClientDevice)x) .MergeChangeSets(BaseStations.Transform(x => (IClientDevice)x)) .MergeChangeSets(Payloads.Transform(x => (IClientDevice)x)) + .MergeChangeSets(RfsaDevices.Transform(x => (IClientDevice)x)) .MergeChangeSets(AdsbDevices.Transform(x => (IClientDevice)x)); #endregion @@ -171,9 +178,9 @@ public MavlinkDevicesService(IConfiguration config, IPacketSequenceCalculator se .Merge(BaseStations.Transform(x => (IClientDevice)x)) .Merge(Payloads.Transform(x => (IClientDevice)x)) .Merge(AdsbDevices.Transform(x => (IClientDevice)x)) - .AutoRefreshOnObservable(_ => _.Name) - .Filter(_ => _.Name.Value != null) - .Transform(_ => _.Name.Value, true) + .AutoRefreshOnObservable(d => d.Name) + .Filter(d => d.Name.Value != null) + .Transform(d => d.Name.Value, true) .AsObservableCache().DisposeItWith(Disposable); _mavlinkRouter .Filter() @@ -184,6 +191,17 @@ public MavlinkDevicesService(IConfiguration config, IPacketSequenceCalculator se #endregion } + private IRfsaClientDevice CreateRfsaDevice(IMavlinkDevice device) + { + return new RfsaClientDevice(Router, new MavlinkClientIdentity + { + TargetSystemId = device.SystemId, + TargetComponentId = device.ComponentId, + SystemId = _systemId.Value, + ComponentId = _componentId.Value, + }, InternalGetConfig(c => c.Rfsa), _sequenceCalculator, RxApp.MainThreadScheduler); + } + private ISdrClientDevice CreateSdrDevice(IMavlinkDevice device) { var dev = new SdrClientDevice(Router, new MavlinkClientIdentity @@ -192,7 +210,7 @@ private ISdrClientDevice CreateSdrDevice(IMavlinkDevice device) TargetComponentId = device.ComponentId, SystemId = _systemId.Value, ComponentId = _componentId.Value, - }, InternalGetConfig(_ => _.Sdr), _sequenceCalculator, RxApp.MainThreadScheduler); + }, InternalGetConfig(c => c.Sdr), _sequenceCalculator, RxApp.MainThreadScheduler); ((ParamsClientEx)dev.Params).Init(new MavParamByteWiseEncoding(), ArraySegment.Empty); return dev; } @@ -205,7 +223,7 @@ private IGbsClientDevice CreateBaseStation(IMavlinkDevice device) TargetComponentId = device.ComponentId, SystemId = _systemId.Value, ComponentId = _componentId.Value, - }, _sequenceCalculator, InternalGetConfig(_ => _.Gbs)); + }, _sequenceCalculator, InternalGetConfig(c => c.Gbs)); } private IAdsbClientDevice CreateAdsbDevice(IMavlinkDevice device) @@ -216,7 +234,7 @@ private IAdsbClientDevice CreateAdsbDevice(IMavlinkDevice device) TargetComponentId = device.ComponentId, SystemId = _systemId.Value, ComponentId = _componentId.Value, - }, _sequenceCalculator, InternalGetConfig(_ => _.Adsb)); + }, _sequenceCalculator, InternalGetConfig(c => c.Adsb)); } #region Logs @@ -271,37 +289,44 @@ private string TryGetName(StatustextPacket pkt) public IVehicleClient? GetVehicleByFullId(ushort id) { - using var a = Vehicles.BindToObservableList(out var list).Subscribe(); - return list.Items.FirstOrDefault(_ => _.Heartbeat.FullId == id); + using var autoDispose = Vehicles.BindToObservableList(out var list).Subscribe(); + return list.Items.FirstOrDefault(c => c.Heartbeat.FullId == id); } public IObservable> BaseStations { get; } public IGbsClientDevice? GetGbsByFullId(ushort id) { - BaseStations.BindToObservableList(out var list).Subscribe(); - return list.Items.FirstOrDefault(_ => _.Heartbeat.FullId == id); + using var autoDispose = BaseStations.BindToObservableList(out var list).Subscribe(); + return list.Items.FirstOrDefault(d => d.Heartbeat.FullId == id); } public IObservable> Payloads { get; } public ISdrClientDevice? GetPayloadsByFullId(ushort id) { - using var a = Payloads.BindToObservableList(out var list).Subscribe(); - return list.Items.FirstOrDefault(_ => _.Heartbeat.FullId == id); + using var autoDispose = Payloads.BindToObservableList(out var list).Subscribe(); + return list.Items.FirstOrDefault(d => d.Heartbeat.FullId == id); } public IObservable> AdsbDevices { get; } public IAdsbClientDevice? GetAdsbVehicleByFullId(ushort id) { - AdsbDevices.BindToObservableList(out var list).Subscribe(); - return list.Items.FirstOrDefault(_ => _.FullId == id); + using var autoDispose =AdsbDevices.BindToObservableList(out var list).Subscribe(); + return list.Items.FirstOrDefault(d => d.FullId == id); + } + + public IObservable> RfsaDevices { get; } + public IRfsaClientDevice? GetRfsaByFullId(ushort id) + { + using var autoDispose = RfsaDevices.BindToObservableList(out var list).Subscribe(); + return list.Items.FirstOrDefault(d => d.FullId == id); } private IVehicleClient? CreateVehicle(IMavlinkDevice device) { - if (device.Autopilot == MavAutopilot.MavAutopilotArdupilotmega) + //if (device.Autopilot == MavAutopilot.MavAutopilotArdupilotmega) { switch (device.Type) { @@ -314,7 +339,7 @@ private string TryGetName(StatustextPacket pkt) TargetComponentId = device.ComponentId, SystemId = _systemId.Value, ComponentId = _componentId.Value, - }, InternalGetConfig(_ => _.Vehicle), _sequenceCalculator, RxApp.TaskpoolScheduler); + }, InternalGetConfig(c => c.Vehicle), _sequenceCalculator, RxApp.TaskpoolScheduler); case MavType.MavTypeFixedWing: return new ArduPlaneClient(Router, new MavlinkClientIdentity { @@ -322,7 +347,7 @@ private string TryGetName(StatustextPacket pkt) TargetComponentId = device.ComponentId, SystemId = _systemId.Value, ComponentId = _componentId.Value, - }, InternalGetConfig(_ => _.Vehicle), _sequenceCalculator, RxApp.TaskpoolScheduler); + }, InternalGetConfig(c => c.Vehicle), _sequenceCalculator, RxApp.TaskpoolScheduler); default: return null; } diff --git a/src/Asv.Drones.Gui/Services/Plugins/LocalPluginInfo.cs b/src/Asv.Drones.Gui/Services/Plugins/LocalPluginInfo.cs index 0919867f..3559ac48 100644 --- a/src/Asv.Drones.Gui/Services/Plugins/LocalPluginInfo.cs +++ b/src/Asv.Drones.Gui/Services/Plugins/LocalPluginInfo.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using Asv.Common; using Asv.Drones.Gui.Api; using NuGet.Packaging; @@ -22,9 +24,17 @@ public LocalPluginInfo(PackageArchiveReader reader, string pluginFolder, PluginS IsUninstalled = state.IsUninstalled; IsLoaded = state.IsLoaded; LoadingError = state.LoadingError; + var apiPackage = reader.GetPackageDependencies() + .SelectMany(x => x.Packages).FirstOrDefault(x => x.Id == NugetHelper.PluginApiPackageName); + if (apiPackage == null) + { + throw new Exception($"Plugin {Id} does not contain API package as dependency"); + } + ApiVersion = apiPackage.VersionRange.MinVersion?.ToNormalizedString() ?? throw new InvalidOperationException("Api version not found in plugin dependencies"); } public string? SourceUri { get; } + public SemVersion ApiVersion { get; } public string PackageId { get; } public string LocalFolder { get; } public string Title { get; } diff --git a/src/Asv.Drones.Gui/Services/Plugins/NugetHelper.cs b/src/Asv.Drones.Gui/Services/Plugins/NugetHelper.cs index af7fc229..ae11d533 100644 --- a/src/Asv.Drones.Gui/Services/Plugins/NugetHelper.cs +++ b/src/Asv.Drones.Gui/Services/Plugins/NugetHelper.cs @@ -17,6 +17,7 @@ namespace Asv.Drones.Gui; public static class NugetHelper { + public const string PluginApiPackageName = "Asv.Drones.Gui.Api"; public const string NugetPluginName = "Asv.Drones.Gui.Plugin"; public const string NETCoreAppGroup = ".NETCoreApp"; diff --git a/src/Asv.Drones.Gui/Services/Plugins/PluginManager.cs b/src/Asv.Drones.Gui/Services/Plugins/PluginManager.cs index 8a88fac6..e47745a9 100644 --- a/src/Asv.Drones.Gui/Services/Plugins/PluginManager.cs +++ b/src/Asv.Drones.Gui/Services/Plugins/PluginManager.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.Loader; using System.Threading; using System.Threading.Tasks; @@ -61,21 +62,23 @@ public class PluginManager : ServiceWithConfigBase, IPlugin private readonly List _repositories = new(); public const string PluginSearchTermStartWith = "Asv.Drones.Gui.Plugin."; + private const string PluginStateFileName = "__PLUGIN_STATE__"; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly string _sharedPluginFolder; private readonly string _nugetFolder; - private readonly string _nugetCache; private readonly LoggerAdapter _nugetLogger = new(Logger); private readonly SourceCacheContext _cache; - private List _pluginContexts = new(); + private readonly List _pluginContexts = new(); public PluginManager(ContainerConfiguration containerCfg, string localDirectory, IConfiguration configuration) : base(configuration) { + ApiVersion = SemVersion.Parse(typeof(WellKnownUri).Assembly.GetCustomAttribute()!.Version); + #region Servers var servers = InternalGetConfig(x => x.Servers); @@ -146,15 +149,15 @@ public PluginManager(ContainerConfiguration containerCfg, string localDirectory, Logger.Info("Found nuget cache folder {0}", _nugetFolder); } - _nugetCache = Path.Combine(localDirectory, "nuget_cache"); - if (Directory.Exists(_nugetCache) == false) + var nugetCache = Path.Combine(localDirectory, "nuget_cache"); + if (Directory.Exists(nugetCache) == false) { - Logger.Info("Create nuget folder {0}", _nugetCache); - Directory.CreateDirectory(_nugetCache); + Logger.Info("Create nuget folder {0}", nugetCache); + Directory.CreateDirectory(nugetCache); } else { - Logger.Info("Found nuget cache folder {0}", _nugetCache); + Logger.Info("Found nuget cache folder {0}", nugetCache); } #endregion @@ -168,7 +171,7 @@ public PluginManager(ContainerConfiguration containerCfg, string localDirectory, MaxAge = DateTimeOffset.MaxValue*/ }; - + // load all plugins foreach (var dir in Directory.EnumerateDirectories(_sharedPluginFolder, "*", SearchOption.TopDirectoryOnly)) { if (TryGetLocalPluginInfoByFolder(dir, out var info) == false) @@ -181,14 +184,29 @@ public PluginManager(ContainerConfiguration containerCfg, string localDirectory, if (info == null) continue; if (info.IsUninstalled) { - Logger.Info("Remove plugin {0} {1} {2}", info.PackageId, info.Version, info.LocalFolder); + Logger.Info("Remove deleted plugin {0} {1} {2}", info.PackageId, info.Version, info.LocalFolder); Directory.Delete(info.LocalFolder, true); continue; } + // check API version + if (info.ApiVersion.CompareByPrecedence(ApiVersion) != 0) + { + Logger.Warn("Plugin {0} {1} has different API version {2} than application {3}", info.Id, info.Version, + info.ApiVersion, ApiVersion); + SetPluginStateByFolder(info.LocalFolder, x => + { + x.IsLoaded = false; + x.LoadingError = $"Plugin has different API version {info.ApiVersion} than application {ApiVersion}"; + }); + continue; + } + + try { + Logger.Info("Load plugin {0} {1} {2}", info.PackageId, info.Version, info.LocalFolder); _pluginContexts.Add(new PluginAssemblyLoadContext(info.LocalFolder, containerCfg)); SetPluginStateByFolder(info.LocalFolder, x => @@ -202,13 +220,15 @@ public PluginManager(ContainerConfiguration containerCfg, string localDirectory, Logger.Error(e, "Error load plugin {0} {1} {2}", info.PackageId, info.Version, info.LocalFolder); SetPluginStateByFolder(info.LocalFolder, x => { - x.IsLoaded = true; + x.IsLoaded = false; x.LoadingError = e.Message; }); } } } + public SemVersion ApiVersion { get; } + #region Servers public IReadOnlyList Servers @@ -355,9 +375,23 @@ public async Task> Search(SearchQuery query, Ca : PluginSearchTermStartWith + query.Name; var packages = await resource.SearchAsync(searchTerm, filter, query.Skip, query.Take, _nugetLogger, cancel); + foreach (var package in packages) { - result.Add(new PluginSearchInfo(package, repository)); + try + { + var dependencyInfoResource = await repository.GetResourceAsync(cancel); + var dependencyInfo = await dependencyInfoResource.ResolvePackage(package.Identity, NugetHelper.DefaultFramework, + _cache, _nugetLogger, cancel); + if (dependencyInfo == null) + continue; + result.Add(new PluginSearchInfo(package, repository,dependencyInfo)); + } + catch (Exception e) + { + Logger.Warn("Error create plugin search info from {0} {1}", package.Identity.Id, e.Message); + continue; + } if (result.Count >= query.Take) break; } diff --git a/src/Asv.Drones.Gui/Services/Plugins/PluginSearchInfo.cs b/src/Asv.Drones.Gui/Services/Plugins/PluginSearchInfo.cs index 6b0ceb80..f421bb1c 100644 --- a/src/Asv.Drones.Gui/Services/Plugins/PluginSearchInfo.cs +++ b/src/Asv.Drones.Gui/Services/Plugins/PluginSearchInfo.cs @@ -1,3 +1,6 @@ +using System; +using System.Linq; +using Asv.Common; using Asv.Drones.Gui.Api; using NuGet.Protocol.Core.Types; @@ -5,7 +8,8 @@ namespace Asv.Drones.Gui; internal class PluginSearchInfo : IPluginSearchInfo { - public PluginSearchInfo(IPackageSearchMetadata packageSearchMetadata, SourceRepository repository) + public PluginSearchInfo(IPackageSearchMetadata packageSearchMetadata, SourceRepository repository, + SourcePackageDependencyInfo dependencyInfo) { Authors = packageSearchMetadata.Authors; Title = packageSearchMetadata.Identity.Id.Replace(PluginManager.PluginSearchTermStartWith, string.Empty); @@ -15,9 +19,17 @@ public PluginSearchInfo(IPackageSearchMetadata packageSearchMetadata, SourceRepo Description = packageSearchMetadata.Description; Tags = packageSearchMetadata.Tags; DownloadCount = packageSearchMetadata.DownloadCount; + + var apiPackage = dependencyInfo.Dependencies.FirstOrDefault(x => x.Id == NugetHelper.PluginApiPackageName); + if (apiPackage == null) + { + throw new Exception($"Plugin {packageSearchMetadata.Identity.Id} does not contain API package as dependency"); + } + ApiVersion = apiPackage.VersionRange.MinVersion?.ToNormalizedString() ?? throw new InvalidOperationException("Api version not found in plugin dependencies"); } public IPluginServerInfo Source { get; } + public SemVersion ApiVersion { get; } public string PackageId { get; } public string? Title { get; } public string? Authors { get; } @@ -26,22 +38,3 @@ public PluginSearchInfo(IPackageSearchMetadata packageSearchMetadata, SourceRepo public long? DownloadCount { get; } public string? Tags { get; } } - -public class DesginTimePluginSearchInfo : IPluginSearchInfo -{ - public IPluginServerInfo Source { get; } = new DesingTimeSourceInfo(); - public string PackageId { get; set; } - public string? Title { get; set; } - public string? Authors { get; set; } - public string LastVersion { get; set; } - public string Description { get; set; } - public long? DownloadCount { get; set; } - public string? Tags { get; set; } -} - -public class DesingTimeSourceInfo : IPluginServerInfo -{ - public string SourceUri { get; set; } - public string Name { get; set; } - public string? Username { get; set; } -} \ No newline at end of file diff --git a/src/Asv.Drones.Gui/Services/Plugins/PluginWellKnownConst.cs b/src/Asv.Drones.Gui/Services/Plugins/PluginWellKnownConst.cs new file mode 100644 index 00000000..b4a1a71e --- /dev/null +++ b/src/Asv.Drones.Gui/Services/Plugins/PluginWellKnownConst.cs @@ -0,0 +1,2 @@ +namespace Asv.Drones.Gui; + diff --git a/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Adsb/AdsbAnchor.cs b/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Adsb/AdsbAnchor.cs index 9801f9d0..9a1d2283 100644 --- a/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Adsb/AdsbAnchor.cs +++ b/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Adsb/AdsbAnchor.cs @@ -18,7 +18,6 @@ public AdsbAnchor(IAdsbClientDevice src, IAdsbVehicle device) OffsetX = OffsetXEnum.Center; OffsetY = OffsetYEnum.Center; StrokeThickness = 1; - BaseStrokeThickness = 1; Stroke = Brushes.SeaGreen; IconBrush = Brushes.Teal; IsVisible = true; diff --git a/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/Actions/TakeOff/UavActionTakeOff.cs b/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/Actions/TakeOff/UavActionTakeOff.cs index 3eba4733..59202aa8 100644 --- a/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/Actions/TakeOff/UavActionTakeOff.cs +++ b/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/Actions/TakeOff/UavActionTakeOff.cs @@ -33,7 +33,7 @@ public UavActionTakeOff(IVehicleClient vehicle, IMap map, ILogService log, IConf Command = cmd; } - private void OnCommandError(Exception ex) + private void OnCommandError(Exception? ex) { _log.Error("Arm", string.Format(RS.TakeOffAnchorActionViewModel_OnCommandError, Vehicle.Name.Value), ex); // DONE: Localize diff --git a/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/Actions/UavActionBase.cs b/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/Actions/UavActionBase.cs index 7a3abfda..043558f0 100644 --- a/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/Actions/UavActionBase.cs +++ b/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/Actions/UavActionBase.cs @@ -38,7 +38,7 @@ protected UavActionBase(IVehicleClient vehicle, IMap map, ILogService log) protected abstract Task ExecuteImpl(CancellationToken cancel); - protected virtual void OnExecuteError(Exception ex) + protected virtual void OnExecuteError(Exception? ex) { var sender = $"{_vehicle.Name.Value} {Title}"; _log.Error(Title, $"{sender} error", ex); diff --git a/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/FlightUavAnchor.cs b/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/FlightUavAnchor.cs index 989f2da9..72dc04d8 100644 --- a/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/FlightUavAnchor.cs +++ b/src/Asv.Drones.Gui/Shell/Pages/Flight/Anchors/Uav/FlightUavAnchor.cs @@ -30,7 +30,6 @@ public FlightUavAnchor(IVehicleClient vehicle, ILocalizationService loc, OffsetX = OffsetXEnum.Center; OffsetY = OffsetYEnum.Center; StrokeThickness = 1; - BaseStrokeThickness = 1; Stroke = Brushes.Honeydew; IconBrush = Brushes.Red; IsVisible = true; diff --git a/src/Asv.Drones.Gui/Shell/Pages/Flight/Widgets/Uav/FlightUavView.axaml b/src/Asv.Drones.Gui/Shell/Pages/Flight/Widgets/Uav/FlightUavView.axaml index 901aef20..594ba8d8 100644 --- a/src/Asv.Drones.Gui/Shell/Pages/Flight/Widgets/Uav/FlightUavView.axaml +++ b/src/Asv.Drones.Gui/Shell/Pages/Flight/Widgets/Uav/FlightUavView.axaml @@ -36,6 +36,12 @@ +