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-15 21:25:05 +01:00
using System.Collections.ObjectModel ;
using System.Reflection ;
2025-10-16 01:15:13 +01:00
using System.Text ;
2025-10-15 21:25:05 +01:00
using System.Windows.Input ;
2025-10-16 01:15:13 +01:00
using Aaru.CommonTypes ;
using Aaru.CommonTypes.Enums ;
using Aaru.CommonTypes.Interfaces ;
using Aaru.Core ;
2025-10-15 22:05:55 +01:00
using Aaru.Helpers ;
using Aaru.Tui.Models ;
2025-10-16 20:32:46 +01:00
using Aaru.Tui.ViewModels.Dialogs ;
using Aaru.Tui.Views.Dialogs ;
2025-10-16 11:03:35 +01:00
using Aaru.Tui.Views.Windows ;
2025-10-15 21:25:05 +01:00
using Avalonia ;
2025-10-16 11:57:23 +01:00
using Avalonia.Controls ;
2025-10-15 21:25:05 +01:00
using Avalonia.Controls.ApplicationLifetimes ;
2025-10-15 22:05:55 +01:00
using Avalonia.Media ;
2025-10-15 21:25:05 +01:00
using CommunityToolkit.Mvvm.ComponentModel ;
using CommunityToolkit.Mvvm.Input ;
2025-10-16 01:15:13 +01:00
using Humanizer ;
using Humanizer.Bytes ;
2025-10-15 22:05:55 +01:00
using Color = Avalonia . Media . Color ;
2025-10-15 21:25:05 +01:00
namespace Aaru.Tui.ViewModels.Windows ;
2025-10-15 22:05:55 +01:00
public sealed partial class MainWindowViewModel : ViewModelBase
2025-10-15 21:25:05 +01:00
{
2025-10-16 11:57:23 +01:00
readonly Window _view ;
2025-10-15 21:25:05 +01:00
[ObservableProperty]
2025-10-16 00:13:02 +01:00
string _copyright ;
2025-10-15 22:05:55 +01:00
[ObservableProperty]
2025-10-16 00:13:02 +01:00
string _currentPath ;
2025-10-15 22:44:09 +01:00
[ObservableProperty]
2025-10-15 22:05:55 +01:00
ObservableCollection < FileModel > _files = [ ] ;
2025-10-15 21:25:05 +01:00
[ObservableProperty]
2025-10-16 00:13:02 +01:00
string _informationalVersion ;
2025-10-16 01:23:17 +01:00
[ObservableProperty]
bool _isStatusVisible ;
2025-10-16 00:13:02 +01:00
FileModel ? _selectedFile ;
2025-10-16 01:23:17 +01:00
[ObservableProperty]
string _status ;
2025-10-15 21:25:05 +01:00
2025-10-16 11:57:23 +01:00
public MainWindowViewModel ( Window view )
2025-10-15 21:25:05 +01:00
{
2025-10-16 11:57:23 +01:00
_view = view ;
2025-10-15 22:54:35 +01:00
ExitCommand = new RelayCommand ( Exit ) ;
2025-10-16 14:32:27 +01:00
SectorViewCommand = new RelayCommand ( SectorView ) ;
2025-10-16 20:32:46 +01:00
GoToPathCommand = new AsyncRelayCommand ( GoToPathAsync ) ;
2025-10-16 21:14:27 +01:00
HelpCommand = new AsyncRelayCommand ( HelpAsync ) ;
2025-10-15 22:54:35 +01:00
OpenSelectedFileCommand = new RelayCommand ( OpenSelectedFile , CanOpenSelectedFile ) ;
2025-10-15 21:25:05 +01:00
InformationalVersion =
Assembly . GetExecutingAssembly ( )
. GetCustomAttribute < AssemblyInformationalVersionAttribute > ( )
? . InformationalVersion ? ?
"?.?.?" ;
2025-10-15 22:05:55 +01:00
Copyright = Assembly . GetExecutingAssembly ( ) . GetCustomAttribute < AssemblyCopyrightAttribute > ( ) ? . Copyright ? ? "" ;
2025-10-16 01:23:17 +01:00
Status = "Loading..." ;
2025-10-15 21:25:05 +01:00
}
2025-10-16 00:13:02 +01:00
public FileModel ? SelectedFile
{
get = > _selectedFile ;
set
{
SetProperty ( ref _selectedFile , value ) ;
OnPropertyChanged ( nameof ( IsFileInfoAvailable ) ) ;
OnPropertyChanged ( nameof ( SelectedFileIsNotDirectory ) ) ;
OnPropertyChanged ( nameof ( SelectedFileLength ) ) ;
OnPropertyChanged ( nameof ( SelectedFileCreationTime ) ) ;
OnPropertyChanged ( nameof ( SelectedFileLastWriteTime ) ) ;
OnPropertyChanged ( nameof ( SelectedFileAttributes ) ) ;
OnPropertyChanged ( nameof ( SelectedFileUnixMode ) ) ;
2025-10-16 01:15:13 +01:00
OnPropertyChanged ( nameof ( SelectedFileHasInformation ) ) ;
OnPropertyChanged ( nameof ( SelectedFileInformation ) ) ;
2025-10-16 00:13:02 +01:00
}
}
2025-10-15 22:54:35 +01:00
2025-10-16 00:13:02 +01:00
public ICommand OpenSelectedFileCommand { get ; }
public ICommand ExitCommand { get ; }
2025-10-16 14:32:27 +01:00
public ICommand SectorViewCommand { get ; }
2025-10-16 20:32:46 +01:00
public ICommand GoToPathCommand { get ; }
2025-10-16 21:14:27 +01:00
public ICommand HelpCommand { get ; }
2025-10-16 00:13:02 +01:00
public bool IsFileInfoAvailable = > SelectedFile ? . FileInfo ! = null ;
public bool SelectedFileIsNotDirectory = > SelectedFile ? . IsDirectory = = false ;
public long? SelectedFileLength = > SelectedFile ? . IsDirectory = = false ? SelectedFile ? . FileInfo ? . Length : 0 ;
public DateTime ? SelectedFileCreationTime = > SelectedFile ? . FileInfo ? . CreationTime ;
public DateTime ? SelectedFileLastWriteTime = > SelectedFile ? . FileInfo ? . LastWriteTime ;
public string? SelectedFileAttributes = > SelectedFile ? . FileInfo ? . Attributes . ToString ( ) ;
public string? SelectedFileUnixMode = > SelectedFile ? . FileInfo ? . UnixFileMode . ToString ( ) ;
2025-10-16 01:15:13 +01:00
public bool SelectedFileHasInformation = > SelectedFile ? . Information ! = null ;
public string? SelectedFileInformation = > SelectedFile ? . Information ;
2025-10-15 21:25:05 +01:00
2025-10-16 21:14:27 +01:00
Task HelpAsync ( )
{
var dialog = new MainHelpDialog
{
DataContext = new MainHelpDialogViewModel ( null ! )
} ;
// Set the dialog reference after creation
( ( MainHelpDialogViewModel ) dialog . DataContext ! ) . _dialog = dialog ;
return dialog . ShowDialog ( _view ) ;
}
2025-10-16 20:32:46 +01:00
async Task GoToPathAsync ( )
{
var dialog = new GoToPathDialog
{
DataContext = new GoToPathDialogViewModel ( null ! )
} ;
// Set the dialog reference after creation
( ( GoToPathDialogViewModel ) dialog . DataContext ! ) . _dialog = dialog ;
bool? result = await dialog . ShowDialog < bool? > ( _view ) ;
if ( result = = true )
{
var viewModel = ( GoToPathDialogViewModel ) dialog . DataContext ;
if ( viewModel . Path is not null & & Directory . Exists ( viewModel . Path ) )
{
Environment . CurrentDirectory = viewModel . Path ;
LoadFiles ( ) ;
}
}
}
2025-10-16 14:32:27 +01:00
void SectorView ( )
{
if ( SelectedFile ? . ImageFormat is null ) return ;
var view = new HexViewWindow ( ) ;
var vm = new HexViewWindowViewModel ( _view , view , SelectedFile . ImageFormat , SelectedFile . Path ) ;
view . DataContext = vm ;
view . Show ( ) ;
_view . Hide ( ) ;
}
2025-10-15 21:25:05 +01:00
void Exit ( )
{
var lifetime = Application . Current ! . ApplicationLifetime as IControlledApplicationLifetime ;
lifetime ! . Shutdown ( ) ;
}
public void LoadComplete ( )
2025-10-15 22:54:35 +01:00
{
LoadFiles ( ) ;
}
public void LoadFiles ( )
2025-10-15 21:25:05 +01:00
{
2025-10-16 01:23:17 +01:00
IsStatusVisible = true ;
Status = "Loading..." ;
CurrentPath = Directory . GetCurrentDirectory ( ) ;
2025-10-15 22:54:35 +01:00
Files . Clear ( ) ;
2025-10-15 22:44:09 +01:00
2025-10-15 22:05:55 +01:00
var parentDirectory = new FileModel
{
Filename = ".." ,
2025-10-15 22:54:35 +01:00
Path = Path . GetRelativePath ( CurrentPath , ".." ) ,
2025-10-15 22:05:55 +01:00
ForegroundBrush =
new SolidColorBrush ( Color . Parse ( DirColorsParser . Instance . DirectoryColor ? ?
2025-10-15 22:54:35 +01:00
DirColorsParser . Instance . NormalColor ) ) ,
IsDirectory = true
2025-10-15 22:05:55 +01:00
} ;
Files . Add ( parentDirectory ) ;
2025-10-15 22:54:35 +01:00
foreach ( FileModel model in Directory . GetDirectories ( CurrentPath , "*" , SearchOption . TopDirectoryOnly )
. Select ( directory = > new FileModel
{
Path = directory ,
Filename = Path . GetFileName ( directory ) ,
ForegroundBrush =
new SolidColorBrush ( Color . Parse ( DirColorsParser . Instance
. DirectoryColor ? ?
DirColorsParser . Instance . NormalColor ) ) ,
IsDirectory = true
} ) )
2025-10-15 22:05:55 +01:00
Files . Add ( model ) ;
2025-10-15 21:25:05 +01:00
2025-10-15 22:54:35 +01:00
foreach ( string file in Directory . GetFiles ( CurrentPath , "*" , SearchOption . TopDirectoryOnly ) )
2025-10-15 22:05:55 +01:00
{
var model = new FileModel
{
Path = file ,
Filename = Path . GetFileName ( file )
} ;
string extension = Path . GetExtension ( file ) ;
model . ForegroundBrush =
new SolidColorBrush ( Color . Parse ( DirColorsParser . Instance . ExtensionColors . TryGetValue ( extension ,
out string? hex )
? hex
: DirColorsParser . Instance . NormalColor ) ) ;
Files . Add ( model ) ;
}
2025-10-16 00:13:02 +01:00
_ = Task . Run ( Worker ) ;
}
void Worker ( )
{
2025-10-16 01:23:17 +01:00
IsStatusVisible = true ;
Status = "Loading file information..." ;
2025-10-16 01:15:13 +01:00
foreach ( FileModel file in Files )
{
try
{
file . FileInfo = new FileInfo ( file . Path ) ;
IFilter inputFilter = PluginRegister . Singleton . GetFilter ( file . Path ) ;
if ( inputFilter is null ) continue ;
IBaseImage imageFormat = ImageFormat . Detect ( inputFilter ) ;
if ( imageFormat is null ) continue ;
2025-10-16 11:03:35 +01:00
ErrorNumber opened = imageFormat . Open ( inputFilter ) ;
2025-10-16 01:28:50 +01:00
if ( opened ! = ErrorNumber . NoError ) continue ;
2025-10-16 01:15:13 +01:00
StringBuilder sb = new ( ) ;
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . Version ) )
2025-10-17 03:07:02 +01:00
sb . AppendLine ( $"[bold][#875fff]Format:[/] [italic][#af8787]{imageFormat.Format}[/][/] version [#af8787]{imageFormat.Info.Version}[/][/]" ) ;
2025-10-16 01:15:13 +01:00
else
2025-10-17 03:07:02 +01:00
sb . AppendLine ( $"[bold][#875fff]Format:[/] [italic][#af8787]{imageFormat.Format}[/][/][/]" ) ;
2025-10-16 01:15:13 +01:00
switch ( string . IsNullOrWhiteSpace ( imageFormat . Info . Application ) )
{
case false when ! string . IsNullOrWhiteSpace ( imageFormat . Info . ApplicationVersion ) :
2025-10-17 03:07:02 +01:00
sb . AppendLine ( $"[#875fff]Was created with [italic][#af8787]{imageFormat.Info.Application}[/][/] version [italic][#af8787]{imageFormat.Info.ApplicationVersion}[/][/][/]" ) ;
2025-10-16 01:15:13 +01:00
break ;
case false :
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[#875fff]Was created with[/] [italic][#af8787]{imageFormat.Info.Application}[/][/]" ) ;
2025-10-16 01:15:13 +01:00
break ;
}
2025-10-17 03:07:02 +01:00
sb . AppendLine ( $"[#875fff]Image without headers is [lime]{imageFormat.Info.ImageSize}[/] bytes long[/]" ) ;
2025-10-16 01:15:13 +01:00
2025-10-17 03:07:02 +01:00
sb . AppendLine ( $"[#875fff]Contains a media of [lime]{imageFormat.Info.Sectors}[/] sectors[/]" ) ;
sb . AppendLine ( $"[#875fff]Maximum sector size of [teal]{imageFormat.Info.SectorSize}[/] bytes[/]" ) ;
sb . AppendLine ( $"[#875fff]Would be [aqua]{ByteSize.FromBytes(imageFormat.Info.Sectors * imageFormat.Info.SectorSize).Humanize()}[/][/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . Creator ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Created by:[/][/] [green]{imageFormat.Info.Creator}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( imageFormat . Info . CreationTime ! = DateTime . MinValue )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[#875fff]Created on[/] [#afd700]{imageFormat.Info.CreationTime}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( imageFormat . Info . LastModificationTime ! = DateTime . MinValue )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[#875fff]Last modified on[/] [#afd700]{imageFormat.Info.LastModificationTime}[/]" ) ;
2025-10-16 01:15:13 +01:00
2025-10-18 10:24:03 +01:00
sb . AppendLine ( $"[#875fff]Contains a media of type[/] [italic][fuchsia]{imageFormat.Info.MediaType.Humanize()}[/][/]" ) ;
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[#875fff]XML type:[/] [italic][#af8787]{imageFormat.Info.MetadataMediaType}[/][/]" ) ;
2025-10-16 01:15:13 +01:00
2025-10-17 02:57:56 +01:00
sb . AppendLine ( imageFormat . Info . HasPartitions
? "[green]Has partitions[/]"
: "[red]Doesn\'t have partitions[/]" ) ;
2025-10-16 01:15:13 +01:00
2025-10-17 02:57:56 +01:00
sb . AppendLine ( imageFormat . Info . HasSessions
? "[green]Has sessions[/]"
: "[red]Doesn\'t have sessions[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . Comments ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Comments:[/][/] {imageFormat.Info.Comments}" ) ;
2025-10-16 01:15:13 +01:00
if ( imageFormat . Info . MediaSequence ! = 0 & & imageFormat . Info . LastMediaSequence ! = 0 )
2025-10-16 11:03:35 +01:00
{
2025-10-17 03:07:02 +01:00
sb . AppendLine ( $"[#875fff]Media is number [teal]{imageFormat.Info.MediaSequence}[/]" +
"\n" +
$" on a set of [teal]{imageFormat.Info.LastMediaSequence}[/] medias[/]" ) ;
2025-10-16 11:03:35 +01:00
}
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . MediaTitle ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Media title:[/][/] [italic]{imageFormat.Info.MediaTitle}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . MediaManufacturer ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Media manufacturer:[/][/] [italic]{imageFormat.Info.MediaManufacturer}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . MediaModel ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Media model:[/][/] [italic]{imageFormat.Info.MediaModel}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . MediaSerialNumber ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Media serial number:[/][/] [italic]{imageFormat.Info.MediaSerialNumber}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . MediaBarcode ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Media barcode:[/][/] [italic]{imageFormat.Info.MediaBarcode}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . MediaPartNumber ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Media part number:[/][/] [italic]{imageFormat.Info.MediaPartNumber}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . DriveManufacturer ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Drive manufacturer:[/][/] [italic]{imageFormat.Info.DriveManufacturer}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . DriveModel ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Drive model:[/][/] [italic]{imageFormat.Info.DriveModel}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . DriveSerialNumber ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Drive serial number:[/][/] [italic]{imageFormat.Info.DriveSerialNumber}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( ! string . IsNullOrWhiteSpace ( imageFormat . Info . DriveFirmwareRevision ) )
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][#875fff]Drive firmware info:[/][/] [italic]{imageFormat.Info.DriveFirmwareRevision}[/]" ) ;
2025-10-16 01:15:13 +01:00
if ( imageFormat . Info . Cylinders > 0 & &
imageFormat . Info is { Heads : > 0 , SectorsPerTrack : > 0 } & &
imageFormat . Info . MetadataMediaType ! = MetadataMediaType . OpticalDisc & &
imageFormat is not ITapeImage { IsTape : true } )
2025-10-17 03:07:02 +01:00
sb . AppendLine ( $"[bold][#875fff]Media geometry:[/] [italic][teal]{imageFormat.Info.Cylinders}[/] cylinders, [teal]{imageFormat.Info.Heads}[/] heads, [teal]{imageFormat.Info.SectorsPerTrack}[/] sectors per track[/][/][/]" ) ;
2025-10-16 01:15:13 +01:00
if ( imageFormat . Info . ReadableMediaTags is { Count : > 0 } )
{
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][blue]Contains[/] [teal]{imageFormat.Info.ReadableMediaTags.Count}[/] [blue]readable media tags:[/][/]" ) ;
2025-10-16 01:15:13 +01:00
2025-10-17 02:57:56 +01:00
foreach ( MediaTagType tag in imageFormat . Info . ReadableMediaTags . Order ( ) )
sb . Append ( $"[italic][#af8787]{tag}[/][/] " ) ;
2025-10-16 01:15:13 +01:00
sb . AppendLine ( ) ;
}
if ( imageFormat . Info . ReadableSectorTags is { Count : > 0 } )
{
2025-10-17 02:57:56 +01:00
sb . AppendLine ( $"[bold][blue]Contains [teal]{imageFormat.Info.ReadableSectorTags.Count}[/] [blue]readable sector tags:[/][/]" ) ;
2025-10-16 01:15:13 +01:00
2025-10-17 02:57:56 +01:00
foreach ( SectorTagType tag in imageFormat . Info . ReadableSectorTags . Order ( ) )
sb . Append ( $"[italic][#af8787]{tag}[/][/] " ) ;
2025-10-16 01:15:13 +01:00
sb . AppendLine ( ) ;
}
file . Information = sb . ToString ( ) ;
2025-10-16 11:03:35 +01:00
2025-10-16 11:43:03 +01:00
file . ImageFormat = imageFormat as IMediaImage ;
2025-10-16 01:15:13 +01:00
}
catch ( Exception ex )
{
SentrySdk . CaptureException ( ex ) ;
}
}
2025-10-16 01:23:17 +01:00
Status = "Done." ;
IsStatusVisible = false ;
2025-10-15 21:25:05 +01:00
}
2025-10-15 22:54:35 +01:00
void OpenSelectedFile ( )
{
2025-10-16 11:03:35 +01:00
if ( SelectedFile . IsDirectory )
{
CurrentPath = SelectedFile . Path ;
Environment . CurrentDirectory = CurrentPath ;
LoadFiles ( ) ;
2025-10-16 14:32:27 +01:00
return ;
2025-10-16 11:03:35 +01:00
}
2025-10-15 22:54:35 +01:00
2025-10-16 11:03:35 +01:00
if ( SelectedFile . ImageFormat is null ) return ;
2025-10-16 11:57:23 +01:00
var imageWindow = new ImageWindow ( ) ;
2025-10-16 12:06:45 +01:00
var imageViewModel = new ImageWindowViewModel ( _view , imageWindow , SelectedFile . ImageFormat , SelectedFile . Path ) ;
2025-10-16 11:57:23 +01:00
2025-10-16 11:03:35 +01:00
imageWindow . DataContext = imageViewModel ;
imageWindow . Show ( ) ;
2025-10-16 11:57:23 +01:00
_view . Hide ( ) ;
2025-10-15 22:54:35 +01:00
}
bool CanOpenSelectedFile ( ) = > SelectedFile ! = null ;
2025-10-15 21:25:05 +01:00
}