mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
[GUI] Implement MHDD log viewer functionality
This commit is contained in:
207
Aaru.Gui/ViewModels/Windows/MhddLogViewModel.cs
Normal file
207
Aaru.Gui/ViewModels/Windows/MhddLogViewModel.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : MhddLogViewModel.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : GUI view models.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// View model for MHDD 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2025 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Aaru.Gui.Views.Windows;
|
||||
using Aaru.Localization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using JetBrains.Annotations;
|
||||
using MsBox.Avalonia;
|
||||
using MsBox.Avalonia.Enums;
|
||||
using Sentry;
|
||||
|
||||
namespace Aaru.Gui.ViewModels.Windows;
|
||||
|
||||
public partial class MhddLogViewModel : ViewModelBase
|
||||
{
|
||||
readonly MhddLogView _window;
|
||||
[ObservableProperty]
|
||||
string _device;
|
||||
[ObservableProperty]
|
||||
string _filePath;
|
||||
[ObservableProperty]
|
||||
string _firmware;
|
||||
[ObservableProperty]
|
||||
string _mhddVersion;
|
||||
[ObservableProperty]
|
||||
string _scanBlockSize;
|
||||
[ObservableProperty]
|
||||
ObservableCollection<(ulong startingSector, double duration)> _sectorData;
|
||||
[ObservableProperty]
|
||||
string _sectorSize;
|
||||
[ObservableProperty]
|
||||
string _serialNumber;
|
||||
[ObservableProperty]
|
||||
string _totalSectors;
|
||||
|
||||
public MhddLogViewModel(MhddLogView window, [NotNull] string filePath)
|
||||
{
|
||||
_window = window;
|
||||
FilePath = filePath;
|
||||
_sectorData = [];
|
||||
}
|
||||
|
||||
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);
|
||||
var pointer = BitConverter.ToInt32(buffer, 0);
|
||||
int d = stream.ReadByte();
|
||||
int a = stream.ReadByte();
|
||||
stream.ReadExactly(buffer, 0, 4);
|
||||
string ver = Encoding.ASCII.GetString(buffer, 0, 4);
|
||||
|
||||
if(pointer > stream.Length || d != 0x0D || a != 0x0A || ver != "VER:")
|
||||
{
|
||||
stream.Close();
|
||||
|
||||
_ = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
|
||||
UI.The_specified_file_is_not_a_correct_MHDD_log_file,
|
||||
ButtonEnum.Ok,
|
||||
Icon.Error)
|
||||
.ShowWindowDialogAsync(_window);
|
||||
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
stream.Position = 4;
|
||||
|
||||
buffer = new byte[pointer - 4];
|
||||
stream.ReadExactly(buffer, 0, buffer.Length);
|
||||
string header = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
// Parse VER field
|
||||
Match versionMatch = VersionRegex().Match(header);
|
||||
|
||||
if(versionMatch.Success) MhddVersion = $"[green]{versionMatch.Groups[1].Value}[/]";
|
||||
|
||||
// Parse DEVICE field
|
||||
Match deviceMatch = DeviceRegex().Match(header);
|
||||
|
||||
if(deviceMatch.Success) Device = $"[pink]{deviceMatch.Groups[1].Value.Trim()}[/]";
|
||||
|
||||
// Parse F/W field
|
||||
Match firmwareMatch = FirmwareRegex().Match(header);
|
||||
|
||||
if(firmwareMatch.Success) Firmware = $"[rosybrown]{firmwareMatch.Groups[1].Value}[/]";
|
||||
|
||||
// Parse S/N field
|
||||
Match serialMatch = SerialNumberRegex().Match(header);
|
||||
|
||||
if(serialMatch.Success) SerialNumber = $"[purple]{serialMatch.Groups[1].Value}[/]";
|
||||
|
||||
// Parse SECTORS field
|
||||
Match sectorsMatch = SectorsRegex().Match(header);
|
||||
|
||||
if(sectorsMatch.Success)
|
||||
TotalSectors = $"[teal]{ParseNumberWithSeparator(sectorsMatch.Groups[1].Value)}[/]";
|
||||
|
||||
// Parse SECTOR SIZE field
|
||||
Match sectorSizeMatch = SectorSizeRegex().Match(header);
|
||||
|
||||
if(sectorSizeMatch.Success)
|
||||
SectorSize = string.Format(UI._0_bytes_markup,
|
||||
ParseNumberWithSeparator(sectorSizeMatch.Groups[1].Value));
|
||||
|
||||
// Parse SCAN BLOCK SIZE field
|
||||
Match scanBlockMatch = ScanBlockSizeRegex().Match(header);
|
||||
|
||||
if(scanBlockMatch.Success)
|
||||
ScanBlockSize = string.Format(UI._0_sectors_markup,
|
||||
ParseNumberWithSeparator(scanBlockMatch.Groups[1].Value));
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
SentrySdk.CaptureException(ex);
|
||||
|
||||
_ = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
|
||||
string.Format(UI.Error_parsing_MHDD_log_header_0, ex.Message),
|
||||
ButtonEnum.Ok,
|
||||
Icon.Error)
|
||||
.ShowWindowDialogAsync(_window);
|
||||
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
stream.Position = pointer;
|
||||
|
||||
buffer = new byte[8];
|
||||
SectorData.Clear();
|
||||
|
||||
while(stream.Position < stream.Length)
|
||||
{
|
||||
stream.ReadExactly(buffer, 0, 8);
|
||||
var sector = BitConverter.ToUInt64(buffer, 0);
|
||||
stream.ReadExactly(buffer, 0, 8);
|
||||
double duration = BitConverter.ToUInt64(buffer, 0) / 1000.0;
|
||||
SectorData.Add((sector, duration));
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a number string that may contain thousands separators (en-US culture).
|
||||
/// </summary>
|
||||
/// <param name="value">The number string (e.g., "243,587" or "2,448")</param>
|
||||
/// <returns>The parsed number without separators</returns>
|
||||
static ulong ParseNumberWithSeparator(string value) => ulong.Parse(value.Replace(",", ""));
|
||||
|
||||
[GeneratedRegex(@"VER:\s*(\S+)")]
|
||||
private static partial Regex VersionRegex();
|
||||
|
||||
[GeneratedRegex(@"DEVICE:\s*(.+?)(?=\n|$)")]
|
||||
private static partial Regex DeviceRegex();
|
||||
|
||||
[GeneratedRegex(@"F/W:\s*(\S+)")]
|
||||
private static partial Regex FirmwareRegex();
|
||||
|
||||
[GeneratedRegex(@"S/N:\s*(\S+)")]
|
||||
private static partial Regex SerialNumberRegex();
|
||||
|
||||
[GeneratedRegex(@"SECTORS:\s*([\d,]+)")]
|
||||
private static partial Regex SectorsRegex();
|
||||
|
||||
[GeneratedRegex(@"SECTOR SIZE:\s*([\d,]+)\s*bytes")]
|
||||
private static partial Regex SectorSizeRegex();
|
||||
|
||||
[GeneratedRegex(@"SCAN BLOCK SIZE:\s*([\d,]+)\s*sectors")]
|
||||
private static partial Regex ScanBlockSizeRegex();
|
||||
}
|
||||
Reference in New Issue
Block a user