2025-11-18 17:14:48 +00:00
|
|
|
// /***************************************************************************
|
|
|
|
|
// 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)
|
2025-11-24 20:12:10 +00:00
|
|
|
{
|
2025-11-18 17:14:48 +00:00
|
|
|
SectorSize = string.Format(UI._0_bytes_markup,
|
|
|
|
|
ParseNumberWithSeparator(sectorSizeMatch.Groups[1].Value));
|
2025-11-24 20:12:10 +00:00
|
|
|
}
|
2025-11-18 17:14:48 +00:00
|
|
|
|
|
|
|
|
// Parse SCAN BLOCK SIZE field
|
|
|
|
|
Match scanBlockMatch = ScanBlockSizeRegex().Match(header);
|
|
|
|
|
|
|
|
|
|
if(scanBlockMatch.Success)
|
2025-11-24 20:12:10 +00:00
|
|
|
{
|
2025-11-18 17:14:48 +00:00
|
|
|
ScanBlockSize = string.Format(UI._0_sectors_markup,
|
|
|
|
|
ParseNumberWithSeparator(scanBlockMatch.Groups[1].Value));
|
2025-11-24 20:12:10 +00:00
|
|
|
}
|
2025-11-18 17:14:48 +00:00
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
}
|