From 98345425f8801c9bb486d23faa773027e0866057 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Wed, 19 Nov 2025 05:05:56 +0000 Subject: [PATCH] [GUI] Add Device List functionality and remote connection support --- Aaru.Devices/Linux/ListDevices.cs | 6 +- Aaru.Devices/Windows/ListDevices.cs | 4 +- Aaru.Gui/Aaru.Gui.csproj | 1 - Aaru.Gui/Models/DeviceModel.cs | 52 +++++ .../ViewModels/Windows/DeviceListViewModel.cs | 216 ++++++++++++++++++ .../ViewModels/Windows/MainWindowViewModel.cs | 62 ++++- Aaru.Gui/Views/Windows/DeviceList.axaml | 113 +++++++++ Aaru.Gui/Views/Windows/DeviceList.axaml.cs | 57 +++++ Aaru.Gui/Views/Windows/MainWindow.axaml | 9 +- Aaru.Localization/UI.Designer.cs | 96 +++++++- Aaru.Localization/UI.es.resx | 48 +++- Aaru.Localization/UI.resx | 49 +++- 12 files changed, 687 insertions(+), 26 deletions(-) create mode 100644 Aaru.Gui/Models/DeviceModel.cs create mode 100644 Aaru.Gui/ViewModels/Windows/DeviceListViewModel.cs create mode 100644 Aaru.Gui/Views/Windows/DeviceList.axaml create mode 100644 Aaru.Gui/Views/Windows/DeviceList.axaml.cs diff --git a/Aaru.Devices/Linux/ListDevices.cs b/Aaru.Devices/Linux/ListDevices.cs index d6835212f..1c1124888 100644 --- a/Aaru.Devices/Linux/ListDevices.cs +++ b/Aaru.Devices/Linux/ListDevices.cs @@ -39,13 +39,13 @@ using Sentry; namespace Aaru.Devices.Linux; [SupportedOSPlatform("linux")] -static class ListDevices +public static class ListDevices { const string PATH_SYS_DEVBLOCK = "/sys/block/"; /// Gets a list of all known storage devices on Linux /// List of devices - internal static DeviceInfo[] GetList() + public static DeviceInfo[] GetList() { string[] sysdevs = Directory.GetFileSystemEntries(PATH_SYS_DEVBLOCK, "*", SearchOption.TopDirectoryOnly); @@ -66,7 +66,7 @@ static class ListDevices hasUdev = false; } - for(int i = 0; i < sysdevs.Length; i++) + for(var i = 0; i < sysdevs.Length; i++) { devices[i] = new DeviceInfo { diff --git a/Aaru.Devices/Windows/ListDevices.cs b/Aaru.Devices/Windows/ListDevices.cs index 390b65310..e0043e43c 100644 --- a/Aaru.Devices/Windows/ListDevices.cs +++ b/Aaru.Devices/Windows/ListDevices.cs @@ -44,7 +44,7 @@ using Marshal = System.Runtime.InteropServices.Marshal; namespace Aaru.Devices.Windows; [SupportedOSPlatform("windows")] -static class ListDevices +public static class ListDevices { /// Converts a hex dump string to the ASCII string it represents /// Hex dump @@ -63,7 +63,7 @@ static class ListDevices /// Gets a list of all known storage devices on Windows /// List of devices [SuppressMessage("ReSharper", "RedundantCatchClause")] - internal static DeviceInfo[] GetList() + public static DeviceInfo[] GetList() { var deviceIDs = new List(); diff --git a/Aaru.Gui/Aaru.Gui.csproj b/Aaru.Gui/Aaru.Gui.csproj index 0b80491e5..594360b37 100644 --- a/Aaru.Gui/Aaru.Gui.csproj +++ b/Aaru.Gui/Aaru.Gui.csproj @@ -315,7 +315,6 @@ - %(Filename) diff --git a/Aaru.Gui/Models/DeviceModel.cs b/Aaru.Gui/Models/DeviceModel.cs new file mode 100644 index 000000000..2becd4e94 --- /dev/null +++ b/Aaru.Gui/Models/DeviceModel.cs @@ -0,0 +1,52 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : DeviceModel.cs +// Author(s) : Natalia Portillo +// +// Component : GUI data models. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains information about a device. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General public License for more details. +// +// You should have received a copy of the GNU General public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2025 Natalia Portillo +// ****************************************************************************/ + +namespace Aaru.Gui.Models; + +public class DeviceModel +{ + /// Device path + public string Path { get; set; } + /// Device vendor or manufacturer + public string Vendor { get; set; } + /// Device model or product name + public string Model { get; set; } + /// Device serial number + public string Serial { get; set; } + /// Bus the device is attached to + public string Bus { get; set; } + /// + /// Set to true if Aaru can send commands to the device in the current machine or remote, false + /// otherwise + /// + public bool Supported { get; set; } +} \ No newline at end of file diff --git a/Aaru.Gui/ViewModels/Windows/DeviceListViewModel.cs b/Aaru.Gui/ViewModels/Windows/DeviceListViewModel.cs new file mode 100644 index 000000000..9486dd951 --- /dev/null +++ b/Aaru.Gui/ViewModels/Windows/DeviceListViewModel.cs @@ -0,0 +1,216 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : DeviceListViewModel.cs +// Author(s) : Natalia Portillo +// +// Component : GUI view models. +// +// --[ Description ] ---------------------------------------------------------- +// +// View model for device list. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General public License for more details. +// +// You should have received a copy of the GNU General public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2025 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Net.Sockets; +using Aaru.Devices; +using Aaru.Devices.Remote; +using Aaru.Devices.Windows; +using Aaru.Gui.Models; +using Aaru.Gui.Views.Windows; +using Aaru.Localization; +using Aaru.Logging; +using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; +using MsBox.Avalonia; +using MsBox.Avalonia.Base; +using MsBox.Avalonia.Enums; +using Sentry; +using Spectre.Console; + +namespace Aaru.Gui.ViewModels.Windows; + +public partial class DeviceListViewModel : ViewModelBase +{ + readonly DeviceList _window; + [ObservableProperty] + ObservableCollection _devices; + [ObservableProperty] + string _remotePath; + + public DeviceListViewModel(DeviceList window) => _window = window; + + public DeviceListViewModel(DeviceList window, [NotNull] string remotePath) + { + _window = window; + RemotePath = remotePath; + } + + public void LoadData() + { +#pragma warning disable MVVMTK0034 + if(_remotePath != null) +#pragma warning restore MVVMTK0034 + { + LoadRemote(); + + return; + } + + DeviceInfo[] devices = null; + + if(OperatingSystem.IsWindows()) devices = ListDevices.GetList(); + + if(OperatingSystem.IsLinux()) devices = ListDevices.GetList(); + + if((OperatingSystem.IsWindows() || OperatingSystem.IsLinux()) && devices != null) + { + Devices = []; + + foreach(DeviceInfo device in devices) + { + Devices.Add(new DeviceModel + { + Bus = device.Bus, + Model = device.Model, + Path = device.Path, + Serial = device.Serial, + Supported = device.Supported, + Vendor = device.Vendor + }); + } + + return; + } + + AaruLogging.Error(UI.Devices_are_not_supported_on_this_platform); + + IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, + UI + .Devices_are_not_supported_on_this_platform, + ButtonEnum.Ok, + Icon.Error); + + _ = msbox.ShowWindowDialogAsync(_window); + } + + public void LoadRemote() + { + try + { + var aaruUri = new Uri(RemotePath); + + if(aaruUri.Scheme != "aaru" && aaruUri.Scheme != "dic") + { + AaruLogging.Error(UI.Invalid_remote_protocol); + + IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, + UI.Invalid_remote_protocol, + ButtonEnum.Ok, + Icon.Error); + + _ = msbox.ShowWindowDialogAsync(_window); + } + + using var remote = new Remote(aaruUri); + + DeviceInfo[] devices = remote.ListDevices(); + + Devices = []; + + foreach(DeviceInfo device in devices) + { + Devices.Add(new DeviceModel + { + Bus = device.Bus, + Model = device.Model, + Path = device.Path, + Serial = device.Serial, + Supported = device.Supported, + Vendor = device.Vendor + }); + } + } + catch(SocketException ex) + { + if(ex.SocketErrorCode == SocketError.HostNotFound) + { + IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, + UI.Host_not_found, + ButtonEnum.Ok, + Icon.Error); + + _ = msbox.ShowWindowDialogAsync(_window); + } + else + { + SentrySdk.CaptureException(ex); + + AaruLogging.Exception(ex, UI.Error_connecting_to_host); + AaruLogging.Error(UI.Error_connecting_to_host); + + IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, + Markup.Remove(UI.Error_connecting_to_host), + ButtonEnum.Ok, + Icon.Error); + + _ = msbox.ShowWindowDialogAsync(_window); + } + } + + // ReSharper disable once UncatchableException + catch(ArgumentException) + { + IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, + UI.Server_sent_invalid_data, + ButtonEnum.Ok, + Icon.Error); + + _ = msbox.ShowWindowDialogAsync(_window); + } + catch(IOException) + { + IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, + UI.Unknown_network_error, + ButtonEnum.Ok, + Icon.Error); + + _ = msbox.ShowWindowDialogAsync(_window); + } + catch(Exception ex) + { + SentrySdk.CaptureException(ex); + + AaruLogging.Exception(ex, UI.Error_connecting_to_host); + AaruLogging.Error(UI.Error_connecting_to_host); + + IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, + Markup.Remove(UI.Error_connecting_to_host), + ButtonEnum.Ok, + Icon.Error); + + _ = msbox.ShowWindowDialogAsync(_window); + } + } +} \ No newline at end of file diff --git a/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs b/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs index d0d4ce84c..2c384c3ac 100644 --- a/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs +++ b/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs @@ -63,7 +63,9 @@ using CommunityToolkit.Mvvm.Input; using JetBrains.Annotations; using MsBox.Avalonia; using MsBox.Avalonia.Base; +using MsBox.Avalonia.Dto; using MsBox.Avalonia.Enums; +using MsBox.Avalonia.Models; using Spectre.Console; using Console = Aaru.Gui.Views.Dialogs.Console; using FileSystem = Aaru.CommonTypes.AaruMetadata.FileSystem; @@ -115,6 +117,8 @@ public partial class MainWindowViewModel : ViewModelBase DecodeImageMediaTagsCommand = new RelayCommand(DecodeImageMediaTags); OpenMhddLogCommand = new AsyncRelayCommand(OpenMhddLogAsync); OpenIbgLogCommand = new AsyncRelayCommand(OpenIbgLogAsync); + ConnectToRemoteCommand = new AsyncRelayCommand(ConnectToRemoteAsync); + OpenDeviceCommand = new RelayCommand(OpenDevice); _genericHddIcon = new Bitmap(AssetLoader.Open(new Uri("avares://Aaru.Gui/Assets/Icons/oxygen/32x32/drive-harddisk.png"))); @@ -133,7 +137,6 @@ public partial class MainWindowViewModel : ViewModelBase { case PlatformID.Win32NT: case PlatformID.Linux: - case PlatformID.FreeBSD: DevicesSupported = true; break; @@ -168,6 +171,8 @@ public partial class MainWindowViewModel : ViewModelBase public ICommand DecodeImageMediaTagsCommand { get; } public ICommand OpenMhddLogCommand { get; } public ICommand OpenIbgLogCommand { get; } + public ICommand ConnectToRemoteCommand { get; } + public ICommand OpenDeviceCommand { get; } public bool NativeMenuSupported { @@ -225,6 +230,56 @@ public partial class MainWindowViewModel : ViewModelBase } } + void OpenDevice() + { + var deviceListWindow = new DeviceList(); + + deviceListWindow.DataContext = new DeviceListViewModel(deviceListWindow); + + deviceListWindow.Show(); + } + + async Task ConnectToRemoteAsync() + { + IMsBox msbox = MessageBoxManager.GetMessageBoxCustom(new MessageBoxCustomParams + { + ContentTitle = UI.Connect_to_AaruRemote, + ButtonDefinitions = + [ + new ButtonDefinition + { + Name = UI.ButtonLabel_Connect, + IsDefault = true + }, + new ButtonDefinition + { + Name = UI.ButtonLabel_Cancel, + IsCancel = true + } + ], + CanResize = false, + CloseOnClickAway = false, + InputParams = new InputParams + { + Label = UI.Address_IP_or_hostname, + DefaultValue = "", + Multiline = false + }, + ContentMessage = UI.Introduce_AaruRemote_server_address, + MinWidth = 400 + }); + + string result = await msbox.ShowWindowDialogAsync(_view); + + if(result != UI.ButtonLabel_Connect) return; + + var deviceListWindow = new DeviceList(); + + deviceListWindow.DataContext = new DeviceListViewModel(deviceListWindow, msbox.InputValue); + + deviceListWindow.Show(); + } + async Task OpenMhddLogAsync() { IReadOnlyList result = await _view.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions @@ -619,7 +674,7 @@ public partial class MainWindowViewModel : ViewModelBase await dialog.ShowDialog(_view); } - Task SettingsAsync() + internal Task SettingsAsync() { var dialog = new SettingsDialog(); dialog.DataContext = new SettingsViewModel(dialog, false); @@ -627,7 +682,8 @@ public partial class MainWindowViewModel : ViewModelBase return dialog.ShowDialog(_view); } - void Exit() => (Application.Current?.ApplicationLifetime as ClassicDesktopStyleApplicationLifetime)?.Shutdown(); + internal void Exit() => + (Application.Current?.ApplicationLifetime as ClassicDesktopStyleApplicationLifetime)?.Shutdown(); void Console() { diff --git a/Aaru.Gui/Views/Windows/DeviceList.axaml b/Aaru.Gui/Views/Windows/DeviceList.axaml new file mode 100644 index 000000000..1efae94b2 --- /dev/null +++ b/Aaru.Gui/Views/Windows/DeviceList.axaml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Aaru.Gui/Views/Windows/DeviceList.axaml.cs b/Aaru.Gui/Views/Windows/DeviceList.axaml.cs new file mode 100644 index 000000000..908024160 --- /dev/null +++ b/Aaru.Gui/Views/Windows/DeviceList.axaml.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : DeviceList.axaml.cs +// Author(s) : Natalia Portillo +// +// Component : GUI views. +// +// --[ Description ] ---------------------------------------------------------- +// +// View for device list. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General public License for more details. +// +// You should have received a copy of the GNU General public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2025 Natalia Portillo +// ****************************************************************************/ + +using System; +using Aaru.Gui.ViewModels.Windows; +using Avalonia; +using Avalonia.Controls; + +namespace Aaru.Gui.Views.Windows; + +public partial class DeviceList : Window +{ + public DeviceList() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + /// + protected override void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + + if(DataContext is DeviceListViewModel vm) vm?.LoadData(); + } +} \ No newline at end of file diff --git a/Aaru.Gui/Views/Windows/MainWindow.axaml b/Aaru.Gui/Views/Windows/MainWindow.axaml index 37ecd4d49..7a93c0e35 100644 --- a/Aaru.Gui/Views/Windows/MainWindow.axaml +++ b/Aaru.Gui/Views/Windows/MainWindow.axaml @@ -23,6 +23,11 @@ Command="{Binding OpenMhddLogCommand, Mode=OneWay}" /> + + - - - _Consola - - - _Dispositivos _Codificaciones @@ -3221,4 +3218,49 @@ Probadores: Abrir registro _IMGBurn + + Conectar a AaruRemote + + + Conectar + + + Dirección (IP o servidor): + + + Introduzca la dirección del servidor AaruRemote. + + + Conectar a Aaru_Remote + + + Abrir _dispositivo... + + + Lista de dispositivos + + + [bold][slateblue1]Servidor AaruRemote[/][/] + + + [bold][slateblue1]Ruta[/][/] + + + [bold][slateblue1]Soportado[/][/] + + + Los dispositivos no están soportados en esta plataforma. + + + Protocolo remoto inválido. + + + Servidor no encontrado. + + + El servidor envió datos inválidos. + + + Error de red desconocido. + \ No newline at end of file diff --git a/Aaru.Localization/UI.resx b/Aaru.Localization/UI.resx index dc9afc90d..e0a0f53b5 100644 --- a/Aaru.Localization/UI.resx +++ b/Aaru.Localization/UI.resx @@ -2828,10 +2828,6 @@ Do you want to continue? E_xit '_' is the character that will be used for the keyboard accelerator - - - _Devices - '_' is the character that will be used for the keyboard accelerator _Refresh @@ -3297,4 +3293,49 @@ Do you want to continue? Open _IMGBurn log + + Connect to AaruRemote + + + Connect + + + Address (IP or hostname): + + + Introduce AaruRemote server address. + + + Connect to AaruRemote + + + Open _device... + + + Device list + + + [bold][slateblue1]AaruRemote server[/][/] + + + [bold][slateblue1]Path[/][/] + + + [bold][slateblue1]Supported[/][/] + + + Devices are not supported on this platform. + + + Invalid remote protocol. + + + Host not found. + + + Server sent invalid data. + + + Unknown network error. + \ No newline at end of file