diff --git a/.idea/.idea.Aaru/.idea/contentModel.xml b/.idea/.idea.Aaru/.idea/contentModel.xml index b038b7f44..da89e13c4 100644 --- a/.idea/.idea.Aaru/.idea/contentModel.xml +++ b/.idea/.idea.Aaru/.idea/contentModel.xml @@ -1,27 +1,28 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - @@ -70,6 +71,13 @@ + + + + + + + @@ -80,18 +88,12 @@ - - - - - - @@ -100,13 +102,6 @@ - - - - - - - @@ -114,12 +109,19 @@ + + + + + + + + - @@ -178,13 +180,6 @@ - - - - - - - @@ -205,10 +200,19 @@ + + + + + + + + + @@ -217,7 +221,6 @@ - @@ -235,7 +238,6 @@ - @@ -256,8 +258,8 @@ - + @@ -267,11 +269,11 @@ - + - + @@ -285,17 +287,17 @@ - - + + - + @@ -315,19 +317,12 @@ - + - - - - - - - @@ -344,61 +339,68 @@ + + + + + + + + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + @@ -423,6 +425,7 @@ + @@ -434,12 +437,11 @@ - - + @@ -461,9 +463,9 @@ - + @@ -493,13 +495,6 @@ - - - - - - - @@ -563,11 +558,11 @@ - + @@ -587,10 +582,17 @@ + + + + + + + + - @@ -647,13 +649,6 @@ - - - - - - - @@ -666,17 +661,28 @@ - + + + + + + + + + - + + + + @@ -684,16 +690,13 @@ - - - + + - - @@ -729,13 +732,11 @@ - - - + @@ -743,14 +744,11 @@ + - - - - @@ -790,8 +788,8 @@ - + @@ -800,8 +798,8 @@ - + @@ -810,8 +808,8 @@ - + @@ -838,15 +836,8 @@ - - - - - - - - + @@ -864,11 +855,11 @@ + - @@ -891,17 +882,34 @@ + + + + + + + + + + + + - + + + + + + @@ -909,18 +917,12 @@ - - - - - - @@ -1014,12 +1016,6 @@ - - - - - - @@ -1044,6 +1040,12 @@ + + + + + + @@ -1126,8 +1128,6 @@ - - @@ -1143,18 +1143,6 @@ - - - - - - - - - - - - @@ -1167,6 +1155,18 @@ + + + + + + + + + + + + @@ -1175,14 +1175,14 @@ + + - - @@ -1205,10 +1205,12 @@ + + + - @@ -1241,26 +1243,19 @@ - + + + + - - - - - - - - - - @@ -1329,6 +1324,17 @@ + + + + + + + + + + + @@ -1337,7 +1343,6 @@ - @@ -1345,6 +1350,10 @@ + + + + @@ -1352,9 +1361,6 @@ - - - @@ -1437,7 +1443,16 @@ - + + + + + + + + + + @@ -1459,16 +1474,6 @@ - - - - - - - - - - @@ -1502,6 +1507,16 @@ + + + + + + + + + + @@ -1539,16 +1554,6 @@ - - - - - - - - - - @@ -1639,8 +1644,8 @@ - + @@ -1674,17 +1679,6 @@ - - - - - - - - - - - @@ -1695,12 +1689,16 @@ - - - - - - + + + + + + + + + + @@ -1884,16 +1882,6 @@ - - - - - - - - - - @@ -1904,6 +1892,16 @@ + + + + + + + + + + @@ -1922,6 +1920,14 @@ + + + + + + + + @@ -1929,7 +1935,6 @@ - @@ -1937,13 +1942,6 @@ - - - - - - - @@ -1953,11 +1951,20 @@ - + + + + + + + + + + @@ -1966,13 +1973,19 @@ - - - + - + + + + + + + + + @@ -2006,8 +2019,8 @@ - + @@ -2015,7 +2028,7 @@ - + @@ -2038,14 +2051,7 @@ - - - - - - - \ No newline at end of file diff --git a/Aaru.Gui/Models/FileSystemModel.cs b/Aaru.Gui/Models/FileSystemModel.cs new file mode 100644 index 000000000..a1652bc28 --- /dev/null +++ b/Aaru.Gui/Models/FileSystemModel.cs @@ -0,0 +1,11 @@ +using Aaru.CommonTypes.Interfaces; + +namespace Aaru.Gui.Models +{ + public class FileSystemModel : RootModel + { + public string VolumeName { get; set; } + public IFilesystem Filesystem { get; set; } + public IReadOnlyFilesystem ReadOnlyFilesystem { get; set; } + } +} \ No newline at end of file diff --git a/Aaru.Gui/Models/ImageModel.cs b/Aaru.Gui/Models/ImageModel.cs index ab5208dad..a78cf0bfe 100644 --- a/Aaru.Gui/Models/ImageModel.cs +++ b/Aaru.Gui/Models/ImageModel.cs @@ -1,4 +1,17 @@ +using System.Collections.ObjectModel; +using Aaru.CommonTypes.Interfaces; +using Avalonia.Media.Imaging; + namespace Aaru.Gui.Models { - public class ImageModel {} + public class ImageModel + { + public ImageModel() => PartitionSchemesOrFileSystems = new ObservableCollection(); + + public string Path { get; set; } + public string FileName { get; set; } + public Bitmap Icon { get; set; } + public ObservableCollection PartitionSchemesOrFileSystems { get; } + public IMediaImage Image { get; set; } + } } \ No newline at end of file diff --git a/Aaru.Gui/Models/PartitionModel.cs b/Aaru.Gui/Models/PartitionModel.cs new file mode 100644 index 000000000..0b6e6feb7 --- /dev/null +++ b/Aaru.Gui/Models/PartitionModel.cs @@ -0,0 +1,16 @@ +using System.Collections.ObjectModel; +using Aaru.CommonTypes; +using Avalonia.Media.Imaging; + +namespace Aaru.Gui.Models +{ + public class PartitionModel + { + public PartitionModel() => FileSystems = new ObservableCollection(); + + public string Name { get; set; } + public Bitmap Icon { get; set; } + public ObservableCollection FileSystems { get; } + public Partition Partition { get; set; } + } +} \ No newline at end of file diff --git a/Aaru.Gui/Models/PartitionSchemeModel.cs b/Aaru.Gui/Models/PartitionSchemeModel.cs new file mode 100644 index 000000000..eeed77b3d --- /dev/null +++ b/Aaru.Gui/Models/PartitionSchemeModel.cs @@ -0,0 +1,13 @@ +using System.Collections.ObjectModel; +using Avalonia.Media.Imaging; + +namespace Aaru.Gui.Models +{ + public class PartitionSchemeModel : RootModel + { + public PartitionSchemeModel() => Partitions = new ObservableCollection(); + + public Bitmap Icon { get; set; } + public ObservableCollection Partitions { get; } + } +} \ No newline at end of file diff --git a/Aaru.Gui/ViewModels/MainWindowViewModel.cs b/Aaru.Gui/ViewModels/MainWindowViewModel.cs index 9e7573131..86f9221c7 100644 --- a/Aaru.Gui/ViewModels/MainWindowViewModel.cs +++ b/Aaru.Gui/ViewModels/MainWindowViewModel.cs @@ -1,21 +1,39 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Reactive; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Interop; +using Aaru.CommonTypes.Structs; +using Aaru.Console; +using Aaru.Core; using Aaru.Database; using Aaru.Gui.Models; using Aaru.Gui.Views; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Media.Imaging; +using Avalonia.Platform; using MessageBox.Avalonia; +using MessageBox.Avalonia.Enums; using ReactiveUI; +using PlatformID = Aaru.CommonTypes.Interop.PlatformID; namespace Aaru.Gui.ViewModels { public class MainWindowViewModel : ViewModelBase { + readonly IAssetLoader _assets; readonly DevicesRootModel _devicesRoot; + readonly Bitmap _genericFolderIcon; + readonly Bitmap _genericHddIcon; + readonly Bitmap _genericOpticalIcon; + readonly Bitmap _genericTapeIcon; readonly ImagesRootModel _imagesRoot; readonly MainWindow _view; ConsoleWindow _consoleWindow; @@ -30,8 +48,10 @@ namespace Aaru.Gui.ViewModels ExitCommand = ReactiveCommand.Create(ExecuteExitCommand); SettingsCommand = ReactiveCommand.Create(ExecuteSettingsCommand); ConsoleCommand = ReactiveCommand.Create(ExecuteConsoleCommand); + OpenCommand = ReactiveCommand.Create(ExecuteOpenCommand); _view = view; TreeRoot = new ObservableCollection(); + _assets = AvaloniaLocator.Current.GetService(); _imagesRoot = new ImagesRootModel { @@ -55,6 +75,18 @@ namespace Aaru.Gui.ViewModels break; } + + _genericHddIcon = + new Bitmap(_assets.Open(new Uri("avares://Aaru.Gui/Assets/Icons/oxygen/32x32/drive-harddisk.png"))); + + _genericOpticalIcon = + new Bitmap(_assets.Open(new Uri("avares://Aaru.Gui/Assets/Icons/oxygen/32x32/drive-optical.png"))); + + _genericTapeIcon = + new Bitmap(_assets.Open(new Uri("avares://Aaru.Gui/Assets/Icons/oxygen/32x32/media-tape.png"))); + + _genericFolderIcon = + new Bitmap(_assets.Open(new Uri("avares://Aaru.Gui/Assets/Icons/oxygen/32x32/inode-directory.png"))); } public bool DevicesSupported @@ -76,6 +108,7 @@ namespace Aaru.Gui.ViewModels public ReactiveCommand StatisticsCommand { get; } public ReactiveCommand ExitCommand { get; } public ReactiveCommand SettingsCommand { get; } + public ReactiveCommand OpenCommand { get; } internal void ExecuteAboutCommand() { @@ -140,5 +173,239 @@ namespace Aaru.Gui.ViewModels _consoleWindow.Show(); } + + async void ExecuteOpenCommand() + { + // TODO: Extensions + var dlgOpenImage = new OpenFileDialog + { + Title = "Choose image to open", AllowMultiple = false + }; + + string[] result = await dlgOpenImage.ShowAsync(_view); + + if(result?.Length != 1) + return; + + var filtersList = new FiltersList(); + IFilter inputFilter = filtersList.GetFilter(result[0]); + + if(inputFilter == null) + { + MessageBoxManager.GetMessageBoxStandardWindow("Error", "Cannot open specified file.", ButtonEnum.Ok, + Icon.Error); + + return; + } + + try + { + IMediaImage imageFormat = ImageFormat.Detect(inputFilter); + + if(imageFormat == null) + { + MessageBoxManager.GetMessageBoxStandardWindow("Error", "Image format not identified.", + ButtonEnum.Ok, Icon.Error); + + return; + } + + AaruConsole.WriteLine("Image format identified by {0} ({1}).", imageFormat.Name, imageFormat.Id); + + try + { + if(!imageFormat.Open(inputFilter)) + { + MessageBoxManager.GetMessageBoxStandardWindow("Error", "Unable to open image format.", + ButtonEnum.Ok, Icon.Error); + + AaruConsole.ErrorWriteLine("Unable to open image format"); + AaruConsole.ErrorWriteLine("No error given"); + + return; + } + + var mediaResource = + new Uri($"avares://Aaru.Gui/Assets/Logos/Media/{imageFormat.Info.MediaType}.png"); + + var imageModel = new ImageModel + { + Path = result[0], Icon = _assets.Exists(mediaResource) + ? new Bitmap(_assets.Open(mediaResource)) + : imageFormat.Info.XmlMediaType == XmlMediaType.BlockMedia + ? _genericHddIcon + : imageFormat.Info.XmlMediaType == XmlMediaType.OpticalDisc + ? _genericOpticalIcon + : _genericFolderIcon, + FileName = Path.GetFileName(result[0]), Image = imageFormat + }; + + // TODO: pnlImageInfo + + List partitions = Core.Partitions.GetAll(imageFormat); + Core.Partitions.AddSchemesToStats(partitions); + + bool checkraw = false; + List idPlugins; + IFilesystem plugin; + PluginBase plugins = GetPluginBase.Instance; + + if(partitions.Count == 0) + { + AaruConsole.DebugWriteLine("Analyze command", "No partitions found"); + + checkraw = true; + } + else + { + AaruConsole.WriteLine("{0} partitions found.", partitions.Count); + + foreach(string scheme in partitions.Select(p => p.Scheme).Distinct().OrderBy(s => s)) + { + // TODO: Add icons to partition schemes + var schemeModel = new PartitionSchemeModel + { + Name = scheme + }; + + foreach(Partition partition in partitions. + Where(p => p.Scheme == scheme).OrderBy(p => p.Start)) + { + // TODO: pnlPartition + var partitionModel = new PartitionModel + { + // TODO: Add icons to partition types + Name = $"{partition.Name} ({partition.Type})", Partition = partition + }; + + AaruConsole.WriteLine("Identifying filesystem on partition"); + + Core.Filesystems.Identify(imageFormat, out idPlugins, partition); + + if(idPlugins.Count == 0) + AaruConsole.WriteLine("Filesystem not identified"); + else + { + AaruConsole.WriteLine($"Identified by {idPlugins.Count} plugins"); + + foreach(string pluginName in idPlugins) + if(plugins.PluginsList.TryGetValue(pluginName, out plugin)) + { + plugin.GetInformation(imageFormat, partition, out string information, null); + + var fsPlugin = plugin as IReadOnlyFilesystem; + + if(fsPlugin != null) + { + Errno error = + fsPlugin.Mount(imageFormat, partition, null, + new Dictionary(), null); + + if(error != Errno.NoError) + fsPlugin = null; + } + + var filesystemModel = new FileSystemModel + { + VolumeName = + plugin.XmlFsType.VolumeName is null ? $"{plugin.XmlFsType.Type}" + : $"{plugin.XmlFsType.VolumeName} ({plugin.XmlFsType.Type})", + Filesystem = plugin, ReadOnlyFilesystem = fsPlugin + }; + + /* TODO: Trap expanding item + if(fsPlugin != null) + Statistics.AddCommand("ls"); + */ + + Statistics.AddFilesystem(plugin.XmlFsType.Type); + partitionModel.FileSystems.Add(filesystemModel); + } + } + + schemeModel.Partitions.Add(partitionModel); + } + + imageModel.PartitionSchemesOrFileSystems.Add(schemeModel); + } + } + + if(checkraw) + { + var wholePart = new Partition + { + Name = "Whole device", Length = imageFormat.Info.Sectors, + Size = imageFormat.Info.Sectors * imageFormat.Info.SectorSize + }; + + Core.Filesystems.Identify(imageFormat, out idPlugins, wholePart); + + if(idPlugins.Count == 0) + AaruConsole.WriteLine("Filesystem not identified"); + else + { + AaruConsole.WriteLine($"Identified by {idPlugins.Count} plugins"); + + foreach(string pluginName in idPlugins) + if(plugins.PluginsList.TryGetValue(pluginName, out plugin)) + { + plugin.GetInformation(imageFormat, wholePart, out string information, null); + + var fsPlugin = plugin as IReadOnlyFilesystem; + + if(fsPlugin != null) + { + Errno error = fsPlugin.Mount(imageFormat, wholePart, null, + new Dictionary(), null); + + if(error != Errno.NoError) + fsPlugin = null; + } + + var filesystemModel = new FileSystemModel + { + VolumeName = plugin.XmlFsType.VolumeName is null ? $"{plugin.XmlFsType.Type}" + : $"{plugin.XmlFsType.VolumeName} ({plugin.XmlFsType.Type})", + Filesystem = plugin, ReadOnlyFilesystem = fsPlugin + }; + + /* TODO: Trap expanding item + if(fsPlugin != null) + Statistics.AddCommand("ls"); + */ + + Statistics.AddFilesystem(plugin.XmlFsType.Type); + imageModel.PartitionSchemesOrFileSystems.Add(filesystemModel); + } + } + } + + Statistics.AddMediaFormat(imageFormat.Format); + Statistics.AddMedia(imageFormat.Info.MediaType, false); + Statistics.AddFilter(inputFilter.Name); + + _imagesRoot.Images.Add(imageModel); + } + catch(Exception ex) + { + MessageBoxManager.GetMessageBoxStandardWindow("Error", "Unable to open image format.", + ButtonEnum.Ok, Icon.Error); + + AaruConsole.ErrorWriteLine("Unable to open image format"); + AaruConsole.ErrorWriteLine("Error: {0}", ex.Message); + AaruConsole.DebugWriteLine("Image-info command", "Stack trace: {0}", ex.StackTrace); + } + } + catch(Exception ex) + { + MessageBoxManager.GetMessageBoxStandardWindow("Error", "Exception reading file.", ButtonEnum.Ok, + Icon.Error); + + AaruConsole.ErrorWriteLine($"Error reading file: {ex.Message}"); + AaruConsole.DebugWriteLine("Image-info command", ex.StackTrace); + } + + Statistics.AddCommand("image-info"); + } } } \ No newline at end of file diff --git a/Aaru.Gui/Views/MainWindow.xaml b/Aaru.Gui/Views/MainWindow.xaml index 1f94fa78c..1fa34b5f2 100644 --- a/Aaru.Gui/Views/MainWindow.xaml +++ b/Aaru.Gui/Views/MainWindow.xaml @@ -10,7 +10,7 @@ - + @@ -49,6 +49,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Aaru/Main.cs b/Aaru/Main.cs index 01c7e45ed..68d9b0844 100644 --- a/Aaru/Main.cs +++ b/Aaru/Main.cs @@ -36,7 +36,6 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text; -using Aaru.Gui; using Aaru.Commands; using Aaru.Commands.Device; using Aaru.Commands.Filesystem; @@ -45,11 +44,13 @@ using Aaru.Commands.Media; using Aaru.Console; using Aaru.Core; using Aaru.Database; +using Aaru.Gui; using Aaru.Settings; -using Microsoft.EntityFrameworkCore; using Avalonia; +using Avalonia.Dialogs; using Avalonia.Logging.Serilog; using Avalonia.ReactiveUI; +using Microsoft.EntityFrameworkCore; namespace Aaru { @@ -74,8 +75,7 @@ namespace Aaru if(args.Length == 1 && args[0].ToLowerInvariant() == "gui") { - return BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + return BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } AaruConsole.WriteLineEvent += System.Console.WriteLine; @@ -169,10 +169,8 @@ namespace Aaru } // Avalonia configuration, don't remove; also used by visual designer. - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .UsePlatformDetect() - .LogToDebug() - .UseReactiveUI(); + public static AppBuilder BuildAvaloniaApp() => AppBuilder. + Configure().UsePlatformDetect().LogToDebug(). + UseReactiveUI().UseManagedSystemDialogs(); } } \ No newline at end of file