diff --git a/Aaru.Gui/FilePickerFileTypes.cs b/Aaru.Gui/FilePickerFileTypes.cs index 7ff52475f..0eed4224e 100644 --- a/Aaru.Gui/FilePickerFileTypes.cs +++ b/Aaru.Gui/FilePickerFileTypes.cs @@ -85,4 +85,9 @@ public static class FilePickerFileTypes Patterns = ["*.bin"], MimeTypes = ["application/octet-stream"] }; + + public static FilePickerFileType IbgLogFiles { get; } = new(UI.IMGBurn_Log_Files) + { + Patterns = ["*.ibg"] + }; } \ No newline at end of file diff --git a/Aaru.Gui/ViewModels/Windows/IbgLogViewModel.cs b/Aaru.Gui/ViewModels/Windows/IbgLogViewModel.cs new file mode 100644 index 000000000..c4ce1b53d --- /dev/null +++ b/Aaru.Gui/ViewModels/Windows/IbgLogViewModel.cs @@ -0,0 +1,323 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : IbgLogViewModel.cs +// Author(s) : Natalia Portillo +// +// Component : GUI view models. +// +// --[ Description ] ---------------------------------------------------------- +// +// View model for IMGBurn log viewer. +// +// --[ 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.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Aaru.Gui.Views.Windows; +using Aaru.Localization; +using CommunityToolkit.Mvvm.ComponentModel; +using Humanizer; +using JetBrains.Annotations; +using MsBox.Avalonia; +using MsBox.Avalonia.Enums; + +namespace Aaru.Gui.ViewModels.Windows; + +public partial class IbgLogViewModel : ViewModelBase +{ + readonly IbgLogView _window; + [ObservableProperty] + string _bus; + [ObservableProperty] + string _capacity; + [ObservableProperty] + string _date; + [ObservableProperty] + string _device; + [ObservableProperty] + string _filePath; + [ObservableProperty] + string _firmware; + [ObservableProperty] + string _imagefile; + [ObservableProperty] + ulong _maxSector; + [ObservableProperty] + double _maxSpeed; + [ObservableProperty] + string _mediaSpeeds; + [ObservableProperty] + string _mediaType; + [ObservableProperty] + ulong _sectors; + [ObservableProperty] + string _speedAverage; + [ObservableProperty] + ObservableCollection<(ulong sector, double speedKbps)> _speedData = []; + [ObservableProperty] + string _speedEnd; + [ObservableProperty] + int _speedMultiplier = 1353; + [ObservableProperty] + string _speedStart; + [ObservableProperty] + string _timeTaken; + [ObservableProperty] + string _volumeIdentifier; + + public IbgLogViewModel(IbgLogView window, [NotNull] string filePath) + { + _window = window; + FilePath = filePath; + } + + public void LoadData() + { + Stream stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read); + stream.Seek(0, SeekOrigin.Begin); + var buffer = new byte[4]; + stream.ReadExactly(buffer, 0, 4); + string id = Encoding.ASCII.GetString(buffer); + + if(id != "IBGD") + { + stream.Close(); + + _ = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error, + UI.The_specified_file_is_not_a_correct_IMGBurn_log_file, + ButtonEnum.Ok, + Icon.Error) + .ShowWindowDialogAsync(_window); + + _window.Close(); + } + + stream.Position = 0; + + var sr = new StreamReader(stream); + + var inConfiguration = false; + var inGraphValues = false; + int multiplier; + var ibgCulture = new CultureInfo("en-US"); + string device = null; + string firmware = null; + string bus = null; + DateTime date = DateTime.MinValue; + string mediaSpeeds = null; + string capacity = null; + ulong sectors = 0; + string imagefile = null; + string volumeIdentifier = null; + string mediaType = null; + double speedStart = 0; + double speedEnd = 0; + double speedAverage = 0; + uint timeTaken = 0; + Dictionary speeds = []; + + while(!sr.EndOfStream) + { + string line = sr.ReadLine(); + + if(line == "[START_CONFIGURATION]") + { + inConfiguration = true; + + continue; + } + + if(inConfiguration) + { + if(line.StartsWith("DATE=", StringComparison.Ordinal)) + { + string dateString = line["DATE=".Length..]; + + DateTime.TryParseExact(dateString, + "M/d/yyyy h:mm:ss tt", + ibgCulture, + DateTimeStyles.None, + out date); + } + else if(line.StartsWith("DEVICE_MAKEMODEL=", StringComparison.Ordinal)) + device = line["DEVICE_MAKEMODEL=".Length..]; + else if(line.StartsWith("DEVICE_FIRMWAREVERSION=", StringComparison.Ordinal)) + firmware = line["DEVICE_FIRMWAREVERSION=".Length..]; + else if(line.StartsWith("DEVICE_BUSTYPE=", StringComparison.Ordinal)) + bus = line["DEVICE_BUSTYPE=".Length..]; + else if(line.StartsWith("MEDIA_TYPE=", StringComparison.Ordinal)) + mediaType = line["MEDIA_TYPE=".Length..]; + else if(line.StartsWith("MEDIA_SPEEDS=", StringComparison.Ordinal)) + { + mediaSpeeds = line["MEDIA_SPEEDS=".Length..]; + if(mediaSpeeds == "N/A") mediaSpeeds = null; + } + else if(line.StartsWith("MEDIA_CAPACITY=", StringComparison.Ordinal)) + capacity = line["MEDIA_CAPACITY=".Length..]; + else if(line.StartsWith("DATA_IMAGEFILE", StringComparison.Ordinal)) + { + imagefile = line["DATA_IMAGEFILE=".Length..]; + if(imagefile == "/dev/null") imagefile = null; + } + else if(line.StartsWith("DATA_SECTORS=", StringComparison.Ordinal)) + ulong.TryParse(line["DATA_SECTORS=".Length..], ibgCulture, out sectors); + else if(line.StartsWith("DATA_VOLUMEIDENTIFIER=", StringComparison.Ordinal)) + volumeIdentifier = line["DATA_VOLUMEIDENTIFIER=".Length..]; + else if(line.StartsWith("VERIFY_SPEED_START=", StringComparison.Ordinal)) + double.TryParse(line["VERIFY_SPEED_START=".Length..], ibgCulture, out speedStart); + else if(line.StartsWith("VERIFY_SPEED_END=", StringComparison.Ordinal)) + double.TryParse(line["VERIFY_SPEED_END=".Length..], ibgCulture, out speedEnd); + else if(line.StartsWith("VERIFY_SPEED_AVERAGE=", StringComparison.Ordinal)) + double.TryParse(line["VERIFY_SPEED_AVERAGE=".Length..], ibgCulture, out speedAverage); + else if(line.StartsWith("VERIFY_TIME_TAKEN=", StringComparison.Ordinal)) + uint.TryParse(line["VERIFY_TIME_TAKEN=".Length..], ibgCulture, out timeTaken); + else if(line == "[END_CONFIGURATION]") inConfiguration = false; + + continue; + } + + switch(line) + { + case "[START_VERIFY_GRAPH_VALUES]": + inGraphValues = true; + + continue; + case "[END_VERIFY_GRAPH_VALUES]": + inGraphValues = false; + + continue; + } + + if(!inGraphValues) continue; + + string[] graphValues = line.Split(','); + + if(graphValues.Length == 4 && + ulong.TryParse(graphValues[1], ibgCulture, out ulong sector) && + double.TryParse(graphValues[0], ibgCulture, out double speed)) + speeds[sector] = speed; + } + + double maxSpeedValue = 0; + + switch(mediaType) + { + case "HDD": + multiplier = 1353; + maxSpeedValue = 1500000; // 1500 MB/s cap for graph scaling + + break; + case "PD-650": + case "CD-MO": + case "CD-ROM": + case "CD-R": + case "CD-RW": + case "DDCD-ROM": + case "DDCD-R": + case "DDCD-RW": + multiplier = 150; + maxSpeedValue = 11250; // 52x CD-ROM cap for graph scaling + + break; + case "DVD-ROM": + case "DVD-R": + case "DVD-RAM": + case "DVD-RW": + case "DVD-R DL": + case "DVD-RW DL": + case "DVD-Download": + case "DVD+RW": + case "DVD+R": + case "DVD+RW DL": + case "DVD+R DL": + multiplier = 1353; + maxSpeedValue = 32472; // 24x DVD-ROM cap for graph scaling + + break; + case "BD-ROM": + case "BD-R": + case "BD-RE": + multiplier = 4500; + maxSpeedValue = 108000; // 24x BD-ROM cap for graph scaling + + break; + case "HD DVD-ROM": + case "HD DVD-R": + case "HD DVD-RAM": + case "HD DVD-RW": + case "HD DVD-R DL": + case "HD DVD-RW DL": + multiplier = 4500; + maxSpeedValue = 36550; // 8x HD-DVD cap for graph scaling + + break; + default: + + multiplier = 1353; + + break; + } + + Dictionary fixedSpeeds = []; + + foreach(KeyValuePair kvp in speeds) fixedSpeeds[kvp.Key] = kvp.Value * multiplier; + + speeds = fixedSpeeds; + speedStart *= multiplier; + speedEnd *= multiplier; + speedAverage *= multiplier; + + Device = device != null ? $"[pink]{device}[/]" : null; + Firmware = firmware != null ? $"[rosybrown]{firmware}[/]" : null; + Bus = bus != null ? $"[purple]{bus}[/]" : null; + Date = date != DateTime.MinValue ? $"[yellow]{date}[/]" : null; + MediaSpeeds = mediaSpeeds != null ? $"[red]{mediaSpeeds}[/]" : null; + Capacity = capacity != null ? string.Format(UI._0_sectors_markup, capacity) : null; + Imagefile = imagefile != null ? $"[green]{imagefile}[/]" : null; + VolumeIdentifier = !string.IsNullOrEmpty(volumeIdentifier) ? $"[cyan]{volumeIdentifier}[/]" : null; + MediaType = mediaType != null ? $"[orange]{mediaType}[/]" : null; + SpeedStart = speedStart != 0 ? string.Format(UI._0_N2_KB_s, speedStart) : null; + SpeedEnd = speedEnd != 0 ? string.Format(UI._0_N2_KB_s, speedEnd) : null; + SpeedAverage = speedAverage != 0 ? string.Format(UI._0_N2_KB_s, speedAverage) : null; + TimeTaken = timeTaken != 0 ? $"[aqua]{TimeSpan.FromSeconds(timeTaken).Humanize()}[/]" : null; + + // Populate graph data + SpeedMultiplier = multiplier; + MaxSector = sectors; + + // Find max speed for Y-axis scaling + if(maxSpeedValue == 0) maxSpeedValue = speeds.Select(static kvp => kvp.Value).Prepend(maxSpeedValue).Max(); + + MaxSpeed = maxSpeedValue; + + // Populate speed data for graph + SpeedData.Clear(); + + foreach(KeyValuePair kvp in speeds.OrderBy(static k => k.Key)) + SpeedData.Add((kvp.Key, kvp.Value)); + } +} \ No newline at end of file diff --git a/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs b/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs index 2866906f9..d0d4ce84c 100644 --- a/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs +++ b/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs @@ -114,6 +114,7 @@ public partial class MainWindowViewModel : ViewModelBase ViewImageSectorsCommand = new RelayCommand(ViewImageSectors); DecodeImageMediaTagsCommand = new RelayCommand(DecodeImageMediaTags); OpenMhddLogCommand = new AsyncRelayCommand(OpenMhddLogAsync); + OpenIbgLogCommand = new AsyncRelayCommand(OpenIbgLogAsync); _genericHddIcon = new Bitmap(AssetLoader.Open(new Uri("avares://Aaru.Gui/Assets/Icons/oxygen/32x32/drive-harddisk.png"))); @@ -166,6 +167,7 @@ public partial class MainWindowViewModel : ViewModelBase public ICommand ViewImageSectorsCommand { get; } public ICommand DecodeImageMediaTagsCommand { get; } public ICommand OpenMhddLogCommand { get; } + public ICommand OpenIbgLogCommand { get; } public bool NativeMenuSupported { @@ -227,7 +229,7 @@ public partial class MainWindowViewModel : ViewModelBase { IReadOnlyList result = await _view.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { - Title = UI.Dialog_Choose_image_to_open, + Title = UI.Choose_MHDD_log_to_open, AllowMultiple = false, FileTypeFilter = [FilePickerFileTypes.MhddLogFiles] }); @@ -242,6 +244,26 @@ public partial class MainWindowViewModel : ViewModelBase mhddLogViewWindow.Show(); } + async Task OpenIbgLogAsync() + { + IReadOnlyList result = await _view.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = UI.Choose_IMGBurn_log_to_open, + AllowMultiple = false, + FileTypeFilter = [FilePickerFileTypes.IbgLogFiles] + }); + + // Exit if user did not select exactly one file + if(result.Count != 1) return; + + var ibgLogViewWindow = new IbgLogView(); + + ibgLogViewWindow.DataContext = new IbgLogViewModel(ibgLogViewWindow, result[0].Path.LocalPath); + + ibgLogViewWindow.Show(); + } + + async Task OpenAsync() { // Open file picker dialog to allow user to select an image file diff --git a/Aaru.Gui/Views/Windows/IbgLogView.axaml b/Aaru.Gui/Views/Windows/IbgLogView.axaml new file mode 100644 index 000000000..a48d70fd8 --- /dev/null +++ b/Aaru.Gui/Views/Windows/IbgLogView.axaml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Aaru.Gui/Views/Windows/IbgLogView.axaml.cs b/Aaru.Gui/Views/Windows/IbgLogView.axaml.cs new file mode 100644 index 000000000..7066b4602 --- /dev/null +++ b/Aaru.Gui/Views/Windows/IbgLogView.axaml.cs @@ -0,0 +1,57 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : IbgLogView.axaml.cs +// Author(s) : Natalia Portillo +// +// Component : GUI views. +// +// --[ Description ] ---------------------------------------------------------- +// +// View for IMGBurn log viewer. +// +// --[ 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 IbgLogView : Window +{ + public IbgLogView() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + /// + protected override void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + + if(DataContext is IbgLogViewModel 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 895807a24..37ecd4d49 100644 --- a/Aaru.Gui/Views/Windows/MainWindow.axaml +++ b/Aaru.Gui/Views/Windows/MainWindow.axaml @@ -21,6 +21,8 @@ Command="{Binding OpenCommand, Mode=OneWay}" /> + [slateblue1]Número de serie del medio[/] - [slateblue1]Tipo de medio[/] + [bold][slateblue1]Tipo de medio[/][/] [bold][blue]Mensaje[/][/] @@ -3176,4 +3176,49 @@ Probadores: Error interpretando cabecera del registro MHDD: {0} + + El archivo especificado no es un registro IMGBurn correcto. + + + [lime]{0:N2}[/] [slateblue1]KB/s[/] + + + Elige registro MHDD para abrir + + + Elige registro IMGBurn para abrir + + + Archivos de registro IMGBurn + + + Visor de registros IMGBurn + + + [bold][slateblue1]Velocidades del medio[/][/] + + + [bold][slateblue1]Capacidad del medio[/][/] + + + [bold][slateblue1]Archivo de imagen[/][/] + + + [bold][slateblue1]Identificador del volumen[/][/] + + + [bold][slateblue1]Velocidad de comienzo[/][/] + + + [bold][slateblue1]Velocidad final[/][/] + + + [bold][slateblue1]Velocidad media[/][/] + + + [bold][slateblue1]Tiempo tomado[/][/] + + + Abrir registro _IMGBurn + \ No newline at end of file diff --git a/Aaru.Localization/UI.resx b/Aaru.Localization/UI.resx index 8a4af9b64..dc9afc90d 100644 --- a/Aaru.Localization/UI.resx +++ b/Aaru.Localization/UI.resx @@ -779,7 +779,7 @@ In you are unsure, please press N to not continue. [slateblue1]Last modification time[/] - [slateblue1]Media type[/] + [bold][slateblue1]Media type[/][/] [slateblue1]Image version[/] @@ -3252,4 +3252,49 @@ Do you want to continue? Error parsing MHDD log header: {0} + + The specified file is not a correct IMGBurn log file. + + + [lime]{0:N2}[/] [slateblue1]KB/s[/] + + + Choose MHDD log to open + + + Choose IMGBurn log to open + + + IMGBurn Log Files + + + IMGBurn log viewer + + + [bold][slateblue1]Media speeds[/][/] + + + [bold][slateblue1]Media capacity[/][/] + + + [bold][slateblue1]Image file[/][/] + + + [bold][slateblue1]Volume identifier[/][/] + + + [bold][slateblue1]Speed start[/][/] + + + [bold][slateblue1]Speed end[/][/] + + + [bold][slateblue1]Speed average[/][/] + + + [bold][slateblue1]Time taken[/][/] + + + Open _IMGBurn log + \ No newline at end of file