2025-10-16 16:39:31 +01:00
|
|
|
// /***************************************************************************
|
|
|
|
|
// Aaru Data Preservation Suite
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
|
|
|
|
//
|
|
|
|
|
// Component : Text User Interface.
|
|
|
|
|
//
|
|
|
|
|
// --[ 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
//
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
// Copyright © 2011-2025 Natalia Portillo
|
|
|
|
|
// ****************************************************************************/
|
|
|
|
|
|
2025-10-16 11:43:03 +01:00
|
|
|
using System.Collections.ObjectModel;
|
2025-10-16 12:55:52 +01:00
|
|
|
using System.Text;
|
2025-10-16 11:03:35 +01:00
|
|
|
using System.Windows.Input;
|
2025-10-16 11:43:03 +01:00
|
|
|
using Aaru.CommonTypes;
|
2025-10-16 11:03:35 +01:00
|
|
|
using Aaru.CommonTypes.Interfaces;
|
2025-10-16 11:43:03 +01:00
|
|
|
using Aaru.Tui.Models;
|
2025-10-16 21:20:20 +01:00
|
|
|
using Aaru.Tui.ViewModels.Dialogs;
|
|
|
|
|
using Aaru.Tui.Views.Dialogs;
|
2025-10-16 15:20:06 +01:00
|
|
|
using Aaru.Tui.Views.Windows;
|
2025-10-16 11:03:35 +01:00
|
|
|
using Avalonia;
|
|
|
|
|
using Avalonia.Controls;
|
|
|
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
2025-10-16 11:43:03 +01:00
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
2025-10-16 11:03:35 +01:00
|
|
|
using CommunityToolkit.Mvvm.Input;
|
2025-10-16 12:36:27 +01:00
|
|
|
using Humanizer;
|
|
|
|
|
using Humanizer.Bytes;
|
2025-10-16 12:55:52 +01:00
|
|
|
using Partition = Aaru.CommonTypes.Partition;
|
2025-10-16 11:03:35 +01:00
|
|
|
|
|
|
|
|
namespace Aaru.Tui.ViewModels.Windows;
|
|
|
|
|
|
|
|
|
|
public sealed partial class ImageWindowViewModel : ViewModelBase
|
|
|
|
|
{
|
2025-10-16 11:43:03 +01:00
|
|
|
readonly IMediaImage _imageFormat;
|
2025-10-16 11:57:23 +01:00
|
|
|
readonly Window _parent;
|
2025-10-16 11:43:03 +01:00
|
|
|
readonly Window _view;
|
|
|
|
|
[ObservableProperty]
|
2025-10-16 12:06:45 +01:00
|
|
|
public string _filePath;
|
|
|
|
|
[ObservableProperty]
|
2025-10-16 12:55:52 +01:00
|
|
|
string _filesystemInformation;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
bool _isFilesystemInformationVisible;
|
|
|
|
|
[ObservableProperty]
|
2025-10-16 12:36:27 +01:00
|
|
|
bool _isPartitionInformationVisible;
|
|
|
|
|
[ObservableProperty]
|
2025-10-16 11:43:03 +01:00
|
|
|
bool _isStatusVisible;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
ObservableCollection<FileSystemModelNode> _nodes;
|
|
|
|
|
[ObservableProperty]
|
2025-10-16 12:36:27 +01:00
|
|
|
string _partitionDescription;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
string _partitionLength;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
string _partitionName;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
string _partitionOffset;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
string _partitionScheme;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
string _partitionSequence;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
string _partitionSize;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
string _partitionStart;
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
string _partitionType;
|
|
|
|
|
FileSystemModelNode? _selectedNode;
|
|
|
|
|
[ObservableProperty]
|
2025-10-16 11:43:03 +01:00
|
|
|
string? _status;
|
2025-10-16 11:03:35 +01:00
|
|
|
|
2025-10-16 12:06:45 +01:00
|
|
|
public ImageWindowViewModel(Window parent, Window view, IMediaImage imageFormat, string filePath)
|
2025-10-16 11:03:35 +01:00
|
|
|
{
|
|
|
|
|
_imageFormat = imageFormat;
|
2025-10-16 12:06:45 +01:00
|
|
|
FilePath = filePath;
|
2025-10-16 11:03:35 +01:00
|
|
|
_view = view;
|
2025-10-16 11:57:23 +01:00
|
|
|
_parent = parent;
|
2025-10-16 11:03:35 +01:00
|
|
|
|
2025-10-16 15:20:06 +01:00
|
|
|
ExitCommand = new RelayCommand(Exit);
|
|
|
|
|
BackCommand = new RelayCommand(Back);
|
2025-10-16 21:20:20 +01:00
|
|
|
HelpCommand = new AsyncRelayCommand(HelpAsync);
|
2025-10-16 15:20:06 +01:00
|
|
|
SectorViewCommand = new RelayCommand(SectorView);
|
2025-10-16 11:03:35 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-16 12:36:27 +01:00
|
|
|
public FileSystemModelNode? SelectedNode
|
|
|
|
|
{
|
|
|
|
|
get => _selectedNode;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
SetProperty(ref _selectedNode, value);
|
|
|
|
|
|
|
|
|
|
if(_selectedNode is null) return;
|
|
|
|
|
|
2025-10-16 15:20:06 +01:00
|
|
|
if(_selectedNode.Partition is not null && _selectedNode.Filesystem is null)
|
2025-10-16 12:36:27 +01:00
|
|
|
{
|
|
|
|
|
IsPartitionInformationVisible = true;
|
|
|
|
|
|
|
|
|
|
PartitionSequence = _selectedNode.Partition.Value.Sequence.ToString();
|
|
|
|
|
PartitionName = _selectedNode.Partition.Value.Name;
|
|
|
|
|
PartitionType = _selectedNode.Partition.Value.Type;
|
|
|
|
|
PartitionStart = _selectedNode.Partition.Value.Start.ToString();
|
|
|
|
|
PartitionOffset = ByteSize.FromBytes(_selectedNode.Partition.Value.Offset).Humanize();
|
|
|
|
|
PartitionLength = $"{_selectedNode.Partition.Value.Length} sectors";
|
|
|
|
|
PartitionSize = ByteSize.FromBytes(_selectedNode.Partition.Value.Size).Humanize();
|
|
|
|
|
PartitionScheme = _selectedNode.Partition.Value.Scheme;
|
|
|
|
|
PartitionDescription = _selectedNode.Partition.Value.Description;
|
|
|
|
|
|
|
|
|
|
OnPropertyChanged(nameof(PartitionSequence));
|
|
|
|
|
OnPropertyChanged(nameof(PartitionName));
|
|
|
|
|
OnPropertyChanged(nameof(PartitionType));
|
|
|
|
|
OnPropertyChanged(nameof(PartitionStart));
|
|
|
|
|
OnPropertyChanged(nameof(PartitionOffset));
|
|
|
|
|
OnPropertyChanged(nameof(PartitionLength));
|
|
|
|
|
OnPropertyChanged(nameof(PartitionSize));
|
|
|
|
|
OnPropertyChanged(nameof(PartitionScheme));
|
|
|
|
|
OnPropertyChanged(nameof(PartitionDescription));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
IsPartitionInformationVisible = false;
|
|
|
|
|
|
2025-10-16 12:55:52 +01:00
|
|
|
if(_selectedNode.Filesystem is not null)
|
|
|
|
|
{
|
|
|
|
|
IsFilesystemInformationVisible = true;
|
|
|
|
|
FilesystemInformation = _selectedNode.FilesystemInformation ?? "";
|
|
|
|
|
|
|
|
|
|
OnPropertyChanged(nameof(FilesystemInformation));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
IsFilesystemInformationVisible = false;
|
|
|
|
|
|
2025-10-16 12:36:27 +01:00
|
|
|
OnPropertyChanged(nameof(IsPartitionInformationVisible));
|
2025-10-16 12:55:52 +01:00
|
|
|
OnPropertyChanged(nameof(IsFilesystemInformationVisible));
|
2025-10-16 12:36:27 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 15:20:06 +01:00
|
|
|
public ICommand BackCommand { get; }
|
2025-10-16 21:20:20 +01:00
|
|
|
public ICommand HelpCommand { get; }
|
2025-10-16 15:20:06 +01:00
|
|
|
public ICommand ExitCommand { get; }
|
|
|
|
|
public ICommand SectorViewCommand { get; }
|
|
|
|
|
|
|
|
|
|
void SectorView()
|
|
|
|
|
{
|
|
|
|
|
if(SelectedNode?.Partition is null) return;
|
|
|
|
|
|
|
|
|
|
var view = new HexViewWindow();
|
|
|
|
|
|
|
|
|
|
var vm = new HexViewWindowViewModel(_view, view, _imageFormat, FilePath, SelectedNode.Partition.Value.Start);
|
|
|
|
|
view.DataContext = vm;
|
|
|
|
|
view.Show();
|
|
|
|
|
_view.Hide();
|
|
|
|
|
}
|
2025-10-16 11:03:35 +01:00
|
|
|
|
|
|
|
|
void Back()
|
|
|
|
|
{
|
2025-10-16 11:57:23 +01:00
|
|
|
_parent.Show();
|
2025-10-16 11:03:35 +01:00
|
|
|
_view.Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Exit()
|
|
|
|
|
{
|
|
|
|
|
var lifetime = Application.Current!.ApplicationLifetime as IControlledApplicationLifetime;
|
|
|
|
|
lifetime!.Shutdown();
|
|
|
|
|
}
|
2025-10-16 11:43:03 +01:00
|
|
|
|
|
|
|
|
public void LoadComplete()
|
|
|
|
|
{
|
|
|
|
|
_ = Task.Run(Worker);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 21:20:20 +01:00
|
|
|
Task HelpAsync()
|
|
|
|
|
{
|
|
|
|
|
var dialog = new ImageHelpDialog
|
|
|
|
|
{
|
|
|
|
|
DataContext = new ImageHelpDialogViewModel(null!)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set the dialog reference after creation
|
|
|
|
|
((ImageHelpDialogViewModel)dialog.DataContext!)._dialog = dialog;
|
|
|
|
|
|
|
|
|
|
return dialog.ShowDialog(_view);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 11:43:03 +01:00
|
|
|
void Worker()
|
|
|
|
|
{
|
|
|
|
|
IsStatusVisible = true;
|
|
|
|
|
Status = "Loading partitions...";
|
|
|
|
|
|
2025-10-16 12:06:45 +01:00
|
|
|
Nodes = [];
|
2025-10-16 11:43:03 +01:00
|
|
|
|
|
|
|
|
List<Partition>? partitionsList = Core.Partitions.GetAll(_imageFormat);
|
|
|
|
|
|
|
|
|
|
if(partitionsList.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
partitionsList.Add(new Partition
|
|
|
|
|
{
|
|
|
|
|
Name = Localization.Core.Whole_device,
|
|
|
|
|
Length = _imageFormat.Info.Sectors,
|
|
|
|
|
Size = _imageFormat.Info.Sectors * _imageFormat.Info.SectorSize
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var sequence = 0;
|
|
|
|
|
|
2025-10-16 11:57:23 +01:00
|
|
|
Status = "Loading filesystems...";
|
|
|
|
|
|
|
|
|
|
PluginRegister plugins = PluginRegister.Singleton;
|
|
|
|
|
|
2025-10-16 11:43:03 +01:00
|
|
|
foreach(Partition partition in partitionsList)
|
|
|
|
|
{
|
|
|
|
|
var node = new FileSystemModelNode(partition.Name ?? $"Partition {sequence}")
|
|
|
|
|
{
|
|
|
|
|
Partition = partition
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-16 11:57:23 +01:00
|
|
|
Core.Filesystems.Identify(_imageFormat, out List<string>? idPlugins, partition);
|
|
|
|
|
|
|
|
|
|
if(idPlugins.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var subNodes = new ObservableCollection<FileSystemModelNode>();
|
|
|
|
|
|
|
|
|
|
foreach(string pluginName in idPlugins)
|
|
|
|
|
{
|
|
|
|
|
if(!plugins.Filesystems.TryGetValue(pluginName, out IFilesystem? fs)) continue;
|
|
|
|
|
if(fs is null) continue;
|
|
|
|
|
|
2025-10-16 12:06:45 +01:00
|
|
|
var fsNode = new FileSystemModelNode(fs.Name)
|
|
|
|
|
{
|
2025-10-16 15:20:06 +01:00
|
|
|
Partition = partition,
|
2025-10-16 12:06:45 +01:00
|
|
|
Filesystem = fs
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-16 12:55:52 +01:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
fs.GetInformation(_imageFormat, partition, Encoding.ASCII, out string? information, out _);
|
|
|
|
|
|
|
|
|
|
fsNode.FilesystemInformation = information;
|
|
|
|
|
}
|
|
|
|
|
catch(Exception ex)
|
|
|
|
|
{
|
|
|
|
|
SentrySdk.CaptureException(ex);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 11:57:23 +01:00
|
|
|
subNodes.Add(fsNode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node.SubNodes = subNodes;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 11:43:03 +01:00
|
|
|
Nodes.Add(node);
|
|
|
|
|
sequence++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Status = "Done.";
|
|
|
|
|
IsStatusVisible = false;
|
|
|
|
|
}
|
2025-10-16 11:03:35 +01:00
|
|
|
}
|