using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Input; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.Core; using Aaru.Gui.Views.Windows; using Aaru.Images; using Aaru.Localization; using Aaru.Logging; using Avalonia.Platform.Storage; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Humanizer; using MsBox.Avalonia; using MsBox.Avalonia.Base; using MsBox.Avalonia.Enums; using ImageInfo = Aaru.CommonTypes.Structs.ImageInfo; namespace Aaru.Gui.ViewModels.Windows; public sealed partial class ImageMetadataViewModel : ViewModelBase { readonly ImageMetadata _view; [ObservableProperty] string _comments; [ObservableProperty] bool _commentsNotSet = true; [ObservableProperty] string _creator; [ObservableProperty] bool _creatorNotSet = true; [ObservableProperty] string _driveFirmwareRevision; [ObservableProperty] bool _driveFirmwareRevisionNotSet = true; [ObservableProperty] string _driveManufacturer; [ObservableProperty] bool _driveManufacturerNotSet = true; [ObservableProperty] string _driveModel; [ObservableProperty] bool _driveModelNotSet = true; [ObservableProperty] string _driveSerialNumber; [ObservableProperty] bool _driveSerialNumberNotSet = true; AaruFormat _imageFormat; [ObservableProperty] string _imagePath; IFilter _inputFilter; [ObservableProperty] bool _isOpened; [ObservableProperty] int _mediaLastSequence; [ObservableProperty] string _mediaManufacturer; [ObservableProperty] bool _mediaManufacturerNotSet = true; [ObservableProperty] string _mediaModel; [ObservableProperty] bool _mediaModelNotSet = true; [ObservableProperty] string _mediaPartNumber; [ObservableProperty] bool _mediaPartNumberNotSet = true; [ObservableProperty] int _mediaSequence; [ObservableProperty] string _mediaSerialNumber; [ObservableProperty] bool _mediaSerialNumberNotSet = true; [ObservableProperty] string _mediaTitle; [ObservableProperty] bool _mediaTitleNotSet = true; [ObservableProperty] string _mediaType; [ObservableProperty] bool _sequenceNotSet = true; [ObservableProperty] string _size; public ImageMetadataViewModel(ImageMetadata view) { _view = view; OpenImageCommand = new AsyncRelayCommand(OpenImageAsync); LoadMetadataCommand = new RelayCommand(LoadMetadata); SaveMetadataCommand = new AsyncRelayCommand(SaveMetadataAsync); CloseCommand = new RelayCommand(Close); } public ICommand OpenImageCommand { get; } public ICommand LoadMetadataCommand { get; } public ICommand SaveMetadataCommand { get; } public ICommand CloseCommand { get; } static FilePickerFileType AaruFormatFiles { get; } = new(UI.AaruFormat_files) { Patterns = new AaruFormat().KnownExtensions.Select(static s => $"*{s}").ToList(), MimeTypes = ["application/octet-stream"] }; void Close() { CloseImage(); _view.Close(); } public void CloseImage() { if(!IsOpened) return; _imageFormat.Close(); IsOpened = false; } async Task SaveMetadataAsync() { if(!IsOpened) return; var info = new ImageInfo { MediaSequence = SequenceNotSet ? 0 : MediaSequence, LastMediaSequence = SequenceNotSet ? 0 : MediaLastSequence, Creator = CreatorNotSet ? null : Creator, Comments = CommentsNotSet ? null : Comments, MediaTitle = MediaTitleNotSet ? null : MediaTitle, MediaManufacturer = MediaManufacturerNotSet ? null : MediaManufacturer, MediaModel = MediaModelNotSet ? null : MediaModel, MediaSerialNumber = MediaSerialNumberNotSet ? null : MediaSerialNumber, MediaPartNumber = MediaPartNumberNotSet ? null : MediaPartNumber, DriveManufacturer = DriveManufacturerNotSet ? null : DriveManufacturer, DriveModel = DriveModelNotSet ? null : DriveModel, DriveSerialNumber = DriveSerialNumberNotSet ? null : DriveSerialNumber, DriveFirmwareRevision = DriveFirmwareRevisionNotSet ? null : DriveFirmwareRevision }; ulong sectors = _imageFormat.Info.Sectors; MediaType mediaType = _imageFormat.Info.MediaType; uint negativeSectors = _imageFormat.Info.NegativeSectors; uint overflowSectors = _imageFormat.Info.OverflowSectors; uint sectorSize = _imageFormat.Info.SectorSize; // We close the read-only context and reopen it in resume mode _imageFormat.Close(); bool ret = _imageFormat.Create(ImagePath, mediaType, [], sectors, negativeSectors, overflowSectors, sectorSize); IMsBox msbox; if(!ret) { AaruLogging.Error(UI.Error_reopening_image_for_writing); AaruLogging.Error(_imageFormat.ErrorMessage); msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, UI.There_was_an_error_reopening_the_image_for_writing, ButtonEnum.Ok, Icon.Error); await msbox.ShowAsync(); _view.Close(); return; } // Now we set the metadata _imageFormat.SetImageInfo(info); // We close the image _imageFormat.Close(); // And we re-open it in read-only mode ErrorNumber errno = _imageFormat.Open(_inputFilter); if(errno != ErrorNumber.NoError) { AaruLogging.Error(UI.Error_reopening_image_in_read_only_mode_after_writing_metadata); AaruLogging.Error(Aaru.Localization.Core.Error_0, errno); msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, UI .There_was_an_error_reopening_the_image_in_read_only_mode_after_writing_metadata, ButtonEnum.Ok, Icon.Error); await msbox.ShowAsync(); _view.Close(); return; } msbox = MessageBoxManager.GetMessageBoxStandard(Aaru.Localization.Core.Success, UI.Metadata_saved_successfully, ButtonEnum.Ok, Icon.Success); await msbox.ShowAsync(); LoadMetadata(); } void LoadMetadata() { if(!IsOpened) return; MediaSequence = _imageFormat.Info.MediaSequence; MediaLastSequence = _imageFormat.Info.LastMediaSequence; Creator = _imageFormat.Info.Creator; Comments = _imageFormat.Info.Comments; MediaTitle = _imageFormat.Info.MediaTitle; MediaManufacturer = _imageFormat.Info.MediaManufacturer; MediaModel = _imageFormat.Info.MediaModel; MediaSerialNumber = _imageFormat.Info.MediaSerialNumber; MediaPartNumber = _imageFormat.Info.MediaPartNumber; DriveManufacturer = _imageFormat.Info.DriveManufacturer; DriveModel = _imageFormat.Info.DriveModel; DriveSerialNumber = _imageFormat.Info.DriveSerialNumber; DriveFirmwareRevision = _imageFormat.Info.DriveFirmwareRevision; SequenceNotSet = MediaSequence == 0 || MediaLastSequence == 0; CreatorNotSet = string.IsNullOrEmpty(Creator); CommentsNotSet = string.IsNullOrEmpty(Comments); MediaTitleNotSet = string.IsNullOrEmpty(MediaTitle); MediaManufacturerNotSet = string.IsNullOrEmpty(MediaManufacturer); MediaModelNotSet = string.IsNullOrEmpty(MediaModel); MediaSerialNumberNotSet = string.IsNullOrEmpty(MediaSerialNumber); MediaPartNumberNotSet = string.IsNullOrEmpty(MediaPartNumber); DriveManufacturerNotSet = string.IsNullOrEmpty(DriveManufacturer); DriveModelNotSet = string.IsNullOrEmpty(DriveModel); DriveSerialNumberNotSet = string.IsNullOrEmpty(DriveSerialNumber); DriveFirmwareRevisionNotSet = string.IsNullOrEmpty(DriveFirmwareRevision); } async Task OpenImageAsync() { IReadOnlyList result = await _view.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { Title = UI.Dialog_Choose_image_to_open, AllowMultiple = false, FileTypeFilter = [AaruFormatFiles] }); // Exit if user did not select exactly one file if(result.Count != 1) return; // Get the appropriate filter plugin for the selected file IFilter inputFilter = PluginRegister.Singleton.GetFilter(result[0].Path.LocalPath); // Show error if no suitable filter plugin is found if(inputFilter == null) { IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, UI.Cannot_open_specified_file, ButtonEnum.Ok, Icon.Error); await msbox.ShowAsync(); return; } try { // Detect the image format of the selected file if(ImageFormat.Detect(inputFilter) is not AaruFormat imageFormat) { IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, UI.File_is_not_an_AaruFormat_image, ButtonEnum.Ok, Icon.Error); await msbox.ShowAsync(); return; } try { // Open the image file ErrorNumber opened = imageFormat.Open(inputFilter); if(opened != ErrorNumber.NoError) { IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, string.Format(UI.Error_0_opening_image_format, opened), ButtonEnum.Ok, Icon.Error); await msbox.ShowAsync(); AaruLogging.Error(UI.Unable_to_open_image_format); AaruLogging.Error(UI.No_error_given); return; } if(imageFormat.Info.Version.StartsWith("1.", StringComparison.OrdinalIgnoreCase)) { IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Warning, UI.AaruFormat_images_version_1_x_are_read_only, ButtonEnum.Ok, Icon.Warning); await msbox.ShowAsync(); return; } ImagePath = $"[lime]{result[0].Path.LocalPath}[/]"; MediaType = $"[orange]{imageFormat.Info.MediaType.Humanize()}[/]"; Size = $"[teal]{ByteSize.FromBytes(imageFormat.Info.Sectors * imageFormat.Info.SectorSize).Humanize()}[/]"; _inputFilter = inputFilter; _imageFormat = imageFormat; IsOpened = true; LoadMetadata(); } catch(Exception ex) { IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, UI.Unable_to_open_image_format, ButtonEnum.Ok, Icon.Error); await msbox.ShowAsync(); AaruLogging.Error(UI.Unable_to_open_image_format); AaruLogging.Error(Aaru.Localization.Core.Error_0, ex.Message); AaruLogging.Exception(ex, Aaru.Localization.Core.Error_0, ex.Message); } } catch(Exception ex) { IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, UI.Exception_reading_file, ButtonEnum.Ok, Icon.Error); await msbox.ShowAsync(); AaruLogging.Error(string.Format(UI.Error_reading_file_0, ex.Message)); AaruLogging.Exception(ex, UI.Error_reading_file_0, ex.Message); } } }