mailwolt/app/Livewire/Ui/System/BackupStatusCard.php

751 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Livewire\Ui\System;
use Livewire\Component;
use Illuminate\Support\Str;
use Carbon\Carbon;
class BackupStatusCard extends Component
{
// Anzeige-Felder (nur Ausgabe im Blade)
public ?string $lastAt = null; // "27.10.2025 18:27:35"
public ?string $lastSize = null; // "93.0 MB"
public ?string $lastDuration = null; // "11s" / "2m 03s"
public ?bool $ok = null;
// Progress (nur wenn state=running)
public bool $running = false;
public int $percent = 0;
public ?string $step = null; // z.B. "compress"
protected string $statusFile = '/var/lib/mailwolt/backup.status';
public function mount(): void
{
$this->load();
}
public function render()
{
return view('livewire.ui.system.backup-status-card');
}
public function refresh(): void
{
$this->load(true);
}
public function runNow(): void
{
// asynchron starten (sudoers vorausgesetzt)
@shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &');
// Sofort UI auf „läuft“ setzen Poll holt Echtstatus
$this->running = true;
$this->percent = 1;
$this->step = 'start';
}
private function load(bool $force = false): void
{
$state = $this->readStatus();
// Progress
$this->running = ($state['state'] ?? null) === 'running';
$this->percent = (int)($state['percent'] ?? 0);
$this->step = $state['step'] ?? null;
// Abschlusswerte
$fin = $state['finished_at'] ?? $state['start_at'] ?? null;
$this->lastAt = $fin ? $this->fmtDate($fin) : null;
$sizeB = isset($state['size']) ? (int)$state['size'] : null;
$this->lastSize = $sizeB !== null ? $this->fmtBytes($sizeB) : null;
$durS = isset($state['duration']) ? (int)$state['duration'] : null;
$this->lastDuration = $durS !== null ? $this->fmtDuration($durS) : null;
$this->ok = isset($state['ok']) ? ((string)$state['ok'] === '1') : null;
// Wenn fertig → Balken aus
if (!$this->running) {
$this->percent = 0;
$this->step = null;
}
}
private function readStatus(): array
{
if (!is_readable($this->statusFile)) {
return [];
}
$out = [];
foreach (@file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [] as $line) {
if (!str_contains($line, '=')) continue;
[$k, $v] = array_map('trim', explode('=', $line, 2));
// nur erwartete Keys
if (in_array($k, ['state', 'pid', 'start_at', 'finished_at', 'step', 'percent', 'size', 'duration', 'ok'], true)) {
$out[$k] = $v;
}
}
return $out;
}
private function fmtDate(string $iso): string
{
// in App-Zeitzone anzeigen
$tz = config('app.timezone', 'UTC');
try {
return Carbon::parse($iso)->setTimezone($tz)->format('d.m.Y H:i:s');
} catch (\Throwable) {
return $iso;
}
}
private function fmtBytes(int $bytes): string
{
$u = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
$n = (float)$bytes;
while ($n >= 1024 && $i < count($u) - 1) {
$n /= 1024;
$i++;
}
return number_format($n, ($i <= 1 ? 0 : 1), ',', '.') . ' ' . $u[$i];
}
private function fmtDuration(int $sec): string
{
if ($sec < 60) return $sec . 's';
$m = intdiv($sec, 60);
$s = $sec % 60;
return sprintf('%dm %02ds', $m, $s);
}
}
//namespace App\Livewire\Ui\System;
//
//use Illuminate\Support\Str;
//use Livewire\Component;
//use Carbon\Carbon;
//
//class BackupStatusCard extends Component
//{
// // Anzeige-Felder (fertig formatiert)
// public ?string $lastAt = null;
// public ?string $lastSize = null;
// public ?string $lastDuration = null;
// public ?bool $ok = null;
//
// // Laufstatus für Progress (nur zur Sichtbarkeit)
// public bool $running = false;
// public int $percent = 0;
// public string $progressText = '';
//
// protected string $statusFile = '/var/lib/mailwolt/backup.status';
//
// public function mount(): void
// {
// $this->load();
// }
//
// public function render()
// {
// return view('livewire.ui.system.backup-status-card');
// }
//
// public function refresh(): void
// {
// $this->load(true);
// }
//
// public function runNow(): void
// {
// // asynchron starten sudoers wie bereits gesetzt
// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &');
// // UI direkt "laufend" schalten; echte Werte kommen über Poll
// $this->running = true;
// $this->percent = 1;
// $this->progressText = 'Backup gestartet …';
// }
//
// protected function load(bool $force = false): void
// {
// $s = $this->readStatus();
//
// // Progress
// $state = $s['state'] ?? null;
// $this->running = in_array($state, ['running'], true);
// $this->percent = (int)($s['percent'] ?? 0);
// $step = $s['step'] ?? '';
// $this->progressText = $this->mapStepText($step, $state);
//
// // Abschlusswerte
// $finishedAt = $s['finished_at'] ?? ($state === 'done' ? ($s['start_at'] ?? null) : null);
// $sizeBytes = isset($s['size']) ? (int)$s['size'] : null;
// $durSec = isset($s['duration']) ? (int)$s['duration'] : null;
// $okStr = $s['ok'] ?? null;
//
// $this->lastAt = $finishedAt ? $this->fmtDate($finishedAt) : null;
// $this->lastSize = $sizeBytes !== null ? $this->fmtBytes($sizeBytes) : null;
// $this->lastDuration = $durSec !== null ? $this->fmtDuration($durSec) : null;
// $this->ok = $okStr !== null ? ($okStr === '1' || $okStr === 'true') : null;
// }
//
// protected function readStatus(): array
// {
// if (!is_readable($this->statusFile)) {
// return [];
// }
// $lines = @file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [];
// $out = [];
// foreach ($lines as $ln) {
// if (!str_contains($ln, '=')) continue;
// [$k, $v] = array_map('trim', explode('=', $ln, 2));
// $out[$k] = $v;
// }
// return $out;
// }
//
// private function mapStepText(string $step, ?string $state): string
// {
// if ($state === 'done') return 'Backup abgeschlossen.';
// if ($state === 'failed') return 'Backup fehlgeschlagen.';
// return match ($step) {
// 'start' => 'Backup wird vorbereitet …',
// 'mysqldump' => 'Datenbank wird gesichert …',
// 'maildir' => 'Maildir wird gesichert …',
// 'app' => 'Applikation wird gesichert …',
// 'configs' => 'Konfigurationen werden gesichert …',
// 'compress' => 'Archiv wird komprimiert …',
// 'retention' => 'Alte Backups werden aufgeräumt …',
// default => $step ? Str::headline($step) . ' …' : 'Backup läuft …',
// };
// }
//
// private function fmtDate(string $iso): string
// {
// return Carbon::parse($iso)->timezone(config('app.timezone', 'Europe/Berlin'))->format('d.m.Y H:i:s');
// }
//
// private function fmtBytes(int $b): string
// {
// $units = ['B', 'KB', 'MB', 'GB', 'TB'];
// $i = 0;
// $val = $b;
// while ($val >= 1024 && $i < count($units) - 1) {
// $val /= 1024;
// $i++;
// }
// return number_format($val, $val >= 10 ? 0 : ($val >= 1 ? 1 : 0)) . ' ' . $units[$i];
// }
//
// private function fmtDuration(int $s): string
// {
// if ($s < 60) return $s . 's';
// $m = intdiv($s, 60);
// $r = $s % 60;
// if ($m < 60) return sprintf('%dm %02ds', $m, $r);
// $h = intdiv($m, 60);
// $m = $m % 60;
// return sprintf('%dh %02dm', $h, $m);
// }
//}
//namespace App\Livewire\Ui\System;
//
//use Carbon\Carbon;
//use Livewire\Component;
//
//class BackupStatusCard extends Component
//{
// public string $lastAt = '';
// public string $lastSize = '';
// public string $lastDuration = '';
// public string $statusText = 'unbekannt';
// public string $statusColor = 'text-white/60 border-white/20 bg-white/5';
//
// public string $progressText = '';
// public string $progressPercent = '0';
// public string $progressVisibleClass = 'hidden'; // <- Sichtbarkeit
//
// protected string $statusFile = '/var/lib/mailwolt/backup.status';
//
// public function mount(): void { $this->load(); }
// public function render() { return view('livewire.ui.system.backup-status-card'); }
// public function refresh(): void { $this->load(true); }
//
// public function runNow(): void
// {
// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &');
// // UI sofort auf "läuft" setzen
// $this->progressText = 'Vorbereitung läuft...';
// $this->progressPercent = '1';
// $this->progressVisibleClass = 'block';
// }
//
// protected function load(bool $force = false): void
// {
// $kv = [];
// if (is_file($this->statusFile)) {
// foreach (@file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [] as $ln) {
// $p = strpos($ln, '='); if ($p !== false) $kv[substr($ln,0,$p)] = substr($ln,$p+1);
// }
// }
//
// $state = $kv['state'] ?? null; // running | done | failed
// $step = $kv['step'] ?? null;
// $percent = isset($kv['percent']) ? (int)$kv['percent'] : 0;
// $ok = isset($kv['ok']) ? ((int)$kv['ok'] === 1) : null;
//
// // Anzeigeformatierungen wie gehabt …
// // (deine bestehenden formatBytes/formatDuration/Timezone-Logik)
//
// $this->progressPercent = (string)max(0, min(100, $percent));
// $this->progressText = $this->mapStep($step);
//
// // Sichtbarkeit steuern KEINE Blade-Logik nötig
// if ($state === 'running') {
// $this->progressVisibleClass = 'block';
// } else {
// // bei done/failed: Balken verstecken und auf 100% / finalen Text setzen
// $this->progressVisibleClass = 'hidden';
// if ($percent >= 100 || $step === 'done') {
// $this->progressPercent = '100';
// $this->progressText = 'Backup abgeschlossen.';
// }
// }
//
// // Status-Badge (wie gehabt)
// if ($ok === true) {
// $this->statusText = 'erfolgreich';
// $this->statusColor = 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10';
// } elseif ($ok === false) {
// $this->statusText = 'fehlgeschlagen';
// $this->statusColor = 'text-rose-300 border-rose-400/30 bg-rose-500/10';
// } else {
// $this->statusText = 'unbekannt';
// $this->statusColor = 'text-white/60 border-white/20 bg-white/5';
// }
// }
//
// private function mapStep(?string $step): string
// {
// return match($step) {
// 'mysqldump' => 'Datenbank wird gesichert...',
// 'maildir' => 'Mail-Verzeichnis wird archiviert...',
// 'app' => 'Anwendungsdaten werden gesichert...',
// 'configs' => 'Konfigurationen werden gesichert...',
// 'compress' => 'Backup wird komprimiert...',
// 'retention' => 'Alte Backups werden gelöscht...',
// 'done' => 'Backup abgeschlossen.',
// default => 'Vorbereitung läuft...',
// };
// }
//}
//
//class BackupStatusCard extends Component
//{
// public string $lastAt = '';
// public string $lastSize = '';
// public string $lastDuration = '';
// public string $statusText = 'unbekannt';
// public string $statusColor = 'text-white/60 border-white/20 bg-white/5';
// public string $progressText = '';
// public string $progressPercent = '0';
// public bool $running = false;
// protected string $statusFile = '/var/lib/mailwolt/backup.status';
//
// public function mount(): void
// {
// $this->load();
// }
//
// public function render()
// {
// return view('livewire.ui.system.backup-status-card');
// }
//
// public function refresh(): void
// {
// $this->load(true);
// }
//
// public function runNow(): void
// {
// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &');
// $this->running = true;
// $this->progressText = 'Starte Backup...';
// $this->progressPercent = '1';
// }
//
// protected function load(bool $force = false): void
// {
// if (!is_file($this->statusFile)) {
// $this->running = false;
// return;
// }
//
// $kv = [];
// foreach (@file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [] as $ln) {
// $p = strpos($ln, '=');
// if ($p !== false) {
// $kv[substr($ln, 0, $p)] = substr($ln, $p + 1);
// }
// }
//
// $state = $kv['state'] ?? null;
// $step = $kv['step'] ?? null;
// $percent = isset($kv['percent']) ? (int)$kv['percent'] : 0;
// $ok = isset($kv['ok']) ? ((int)$kv['ok'] === 1) : null;
//
// // Formatierung
// $tz = config('app.timezone', 'Europe/Berlin');
// $finished = $kv['finished_at'] ?? $kv['start_at'] ?? null;
// $this->lastAt = $finished
// ? Carbon::parse($finished)->setTimezone($tz)->format('d.m.Y H:i:s')
// : '';
//
// $this->lastSize = isset($kv['size'])
// ? $this->formatBytes((int)$kv['size'])
// : '';
//
// $this->lastDuration = isset($kv['duration'])
// ? $this->formatDuration((int)$kv['duration'])
// : '';
//
// // Fortschritt
// $this->progressPercent = (string)$percent;
// $this->progressText = $this->mapStep($step);
//
// // Status
// $this->running = ($state === 'running');
//
// if ($ok === true) {
// $this->statusText = 'erfolgreich';
// $this->statusColor = 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10';
// } elseif ($ok === false) {
// $this->statusText = 'fehlgeschlagen';
// $this->statusColor = 'text-rose-300 border-rose-400/30 bg-rose-500/10';
// } else {
// $this->statusText = 'unbekannt';
// $this->statusColor = 'text-white/60 border-white/20 bg-white/5';
// }
// }
//
// private function formatBytes(int $b): string
// {
// if ($b >= 1024 * 1024 * 1024) return number_format($b / (1024 * 1024 * 1024), 1) . ' GB';
// if ($b >= 1024 * 1024) return number_format($b / (1024 * 1024), 1) . ' MB';
// if ($b >= 1024) return number_format($b / 1024, 0) . ' KB';
// return $b . ' B';
// }
//
// private function formatDuration(int $s): string
// {
// if ($s < 60) return $s . 's';
// $m = intdiv($s, 60);
// $r = $s % 60;
// if ($m < 60) return sprintf('%dm %02ds', $m, $r);
// $h = intdiv($m, 60);
// $m = $m % 60;
// return sprintf('%dh %02dm %02ds', $h, $m, $r);
// }
//
// private function mapStep(?string $step): string
// {
// return match ($step) {
// 'mysqldump' => 'Datenbank wird gesichert...',
// 'maildir' => 'Mail-Verzeichnis wird archiviert...',
// 'app' => 'Anwendungsdaten werden gesichert...',
// 'configs' => 'Konfigurationen werden gesichert...',
// 'compress' => 'Backup wird komprimiert...',
// 'retention' => 'Alte Backups werden gelöscht...',
// 'done' => 'Backup abgeschlossen.',
// default => 'Vorbereitung läuft...',
// };
// }
//}
//
////
////
////namespace App\Livewire\Ui\System;
////
////use Carbon\CarbonImmutable;
////use Livewire\Component;
////
////class BackupStatusCard extends Component
////{
//// public ?string $lastAt = null; // finale Zeit
//// public ?string $lastSize = null; // menschenlesbar
//// public ?string $lastDuration = null; // menschenlesbar
//// public ?bool $ok = null;
////
//// // Live-Status
//// public bool $running = false;
//// public ?string $step = null;
//// public int $percent = 0;
////
//// public function mount(): void
//// {
//// $this->load();
//// }
////
//// public function render()
//// {
//// return view('livewire.ui.system.backup-status-card');
//// }
////
//// public function refresh(): void
//// {
//// $this->load(true);
//// }
////
//// public function runNow(): void
//// {
//// // Script asynchron starten (sudoers muss gesetzt sein)
//// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &');
//// // Sofort UI auf "läuft" stellen Poll holt echten Status nach
//// $this->running = true;
//// $this->step = 'start';
//// $this->percent = 1;
//// }
////
//// protected function load(bool $force = false): void
//// {
//// $f = '/var/lib/mailwolt/backup.status';
//// if (!is_file($f)) {
//// return;
//// }
////
//// $data = [];
//// foreach (@file($f, FILE_IGNORE_NEW_LINES) ?: [] as $ln) {
//// if (strpos($ln, '=') !== false) {
//// [$k, $v] = explode('=', $ln, 2);
//// $data[$k] = $v;
//// }
//// }
////
//// $state = $data['state'] ?? null;
//// $this->running = ($state === 'running');
////
//// // Progress
//// $this->step = $data['step'] ?? null;
//// $this->percent = (int)($data['percent'] ?? 0);
////
//// // Finale Werte
//// if ($state === 'done' || $state === 'failed') {
//// $this->ok = ($data['ok'] ?? '') === '1';
//// $ts = $data['finished_at'] ?? $data['start_at'] ?? null;
//// $this->lastAt = $ts ? $this->fmtTs($ts) : null;
////
//// $bytes = (int)($data['size'] ?? 0);
//// $this->lastSize = $bytes ? $this->fmtBytes($bytes) : null;
////
//// $dur = (int)($data['duration'] ?? 0);
//// $this->lastDuration = $dur ? $this->fmtDuration($dur) : null;
//// }
//// }
////
//// protected function fmtTs(string $iso): string
//// {
//// return CarbonImmutable::parse($iso)->tz(config('app.timezone'))
//// ->format('d.m.Y H:i:s');
//// }
////
//// protected function fmtBytes(int $b): string
//// {
//// $u = ['B', 'KB', 'MB', 'GB', 'TB'];
//// $i = 0;
//// while ($b >= 1024 && $i < count($u) - 1) {
//// $b /= 1024;
//// $i++;
//// }
//// return sprintf('%.1f %s', $b, $u[$i]);
//// }
////
//// protected function fmtDuration(int $s): string
//// {
//// if ($s < 60) return $s . 's';
//// $m = intdiv($s, 60);
//// $r = $s % 60;
//// if ($m < 60) return sprintf('%dm %02ds', $m, $r);
//// $h = intdiv($m, 60);
//// $m %= 60;
//// return sprintf('%dh %02dm', $h, $m);
//// }
////}
////
//////
//////namespace App\Livewire\Ui\System;
//////
//////use Carbon\CarbonImmutable;
//////use Illuminate\Support\Str;
//////use Livewire\Component;
//////
//////class BackupStatusCard extends Component
//////{
////// public ?string $lastAt = null; // formatierte Zeit
////// public ?string $lastSize = null; // human readable
////// public ?string $lastDuration = null; // human readable
////// public ?bool $ok = null;
//////
////// // Laufzeit/Progress
////// public string $state = 'idle'; // idle|running|done|error
////// public string $step = ''; // aktueller Schritt
////// public array $steps = [
////// 'mysqldump' => 'Datenbank sichern',
////// 'maildir' => 'Maildir kopieren',
////// 'app' => 'App sichern',
////// 'configs' => 'Configs sichern',
////// 'archive' => 'Archiv erstellen',
////// 'compress' => 'Komprimieren',
////// 'retention' => 'Aufräumen',
////// 'finish' => 'Abschluss',
////// ];
//////
////// protected string $statusFile = '/var/lib/mailwolt/backup.status';
//////
////// public function mount(): void
////// {
////// $this->load(true);
////// }
//////
////// public function render()
////// {
////// return view('livewire.ui.system.backup-status-card');
////// }
//////
////// public function refresh(): void
////// {
////// $this->load(true);
////// }
//////
////// public function runNow(): void
////// {
////// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &');
////// // Sofort in "running" gehen Poll übernimmt dann
////// $this->state = 'running';
////// $this->step = 'mysqldump';
////// }
//////
////// public function load(bool $force = false): void
////// {
////// $raw = $this->readStatus();
////// $this->state = $raw['state'] ?? 'idle';
////// $this->step = $raw['step'] ?? '';
//////
////// // Datum/Zeit hübsch
////// if (!empty($raw['time'])) {
////// $this->lastAt = $this->fmtTime($raw['time']);
////// } else {
////// $this->lastAt = null;
////// }
//////
////// // Größe/Dauer hübsch
////// $bytes = isset($raw['size_bytes']) ? (int)$raw['size_bytes'] : null;
////// $secs = isset($raw['dur_seconds']) ? (int)$raw['dur_seconds'] : null;
//////
////// $this->lastSize = $bytes !== null ? $this->humanBytes($bytes) : null;
////// $this->lastDuration = $secs !== null ? $this->humanDuration($secs) : null;
////// $this->ok = isset($raw['ok']) ? ((string)$raw['ok'] === '1') : null;
////// }
//////
////// protected function readStatus(): array
////// {
////// if (!is_file($this->statusFile)) return [];
////// $out = [];
////// foreach (@file($this->statusFile, FILE_IGNORE_NEW_LINES) ?: [] as $ln) {
////// if (!str_contains($ln, '=')) continue;
////// [$k, $v] = array_map('trim', explode('=', $ln, 2));
////// $out[$k] = $v;
////// }
//////
////// // Backward compatibility (alte Keys)
////// if (isset($out['size']) && !isset($out['size_bytes'])) {
////// $out['size_bytes'] = (int)$out['size'];
////// }
////// if (isset($out['dur']) && !isset($out['dur_seconds'])) {
////// $out['dur_seconds'] = (int)$out['dur'];
////// }
//////
////// return $out;
////// }
//////
////// protected function fmtTime(string $iso): string
////// {
////// try {
////// $tz = config('app.timezone', 'UTC');
////// // ISO aus Script ist idealerweise UTC (Z)
////// $dt = CarbonImmutable::parse($iso)->timezone($tz);
////// // z.B. 27.10.2025, 16:48:02 (CET)
////// return $dt->isoFormat('L, LTS') . ' ' . $dt->format('T');
////// } catch (\Throwable) {
////// return $iso;
////// }
////// }
//////
////// protected function humanBytes(int $bytes): string
////// {
////// $units = ['B', 'KB', 'MB', 'GB', 'TB'];
////// $i = 0;
////// while ($bytes >= 1024 && $i < count($units) - 1) {
////// $bytes /= 1024;
////// $i++;
////// }
////// return number_format($bytes, $i === 0 ? 0 : 1, ',', '.') . ' ' . $units[$i];
////// }
//////
////// protected function humanDuration(int $secs): string
////// {
////// if ($secs < 60) return $secs . ' s';
////// $m = intdiv($secs, 60);
////// $s = $secs % 60;
////// if ($m < 60) return sprintf('%d min %02d s', $m, $s);
////// $h = intdiv($m, 60);
////// $m = $m % 60;
////// return sprintf('%d h %02d min', $h, $m);
////// }
//////}
//////
////////
////////namespace App\Livewire\Ui\System;
////////
////////use Livewire\Component;
////////
////////class BackupStatusCard extends Component
////////{
//////// public ?string $lastAt = null;
//////// public ?string $lastSize = null;
//////// public ?string $lastDuration = null;
//////// public ?bool $ok = null;
////////
//////// public function mount(): void { $this->load(); }
//////// public function render() { return view('livewire.ui.system.backup-status-card'); }
//////// public function refresh(): void { $this->load(true); }
////////
//////// public function runNow(): void
//////// {
//////// @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-backup >/dev/null 2>&1 &');
////////// $this->dispatch('toast', type:'info', title:'Backup gestartet');
//////// }
////////
//////// protected function load(bool $force=false): void
//////// {
//////// // Example: parse a tiny status file your backup script writes.
//////// $f = '/var/lib/mailwolt/backup.status';
//////// if (is_file($f)) {
//////// $lines = @file($f, FILE_IGNORE_NEW_LINES) ?: [];
//////// foreach ($lines as $ln) {
//////// if (str_starts_with($ln,'time=')) $this->lastAt = substr($ln,5);
//////// if (str_starts_with($ln,'size=')) $this->lastSize = substr($ln,5);
//////// if (str_starts_with($ln,'dur=')) $this->lastDuration = substr($ln,4);
//////// if (str_starts_with($ln,'ok=')) $this->ok = (substr($ln,3) === '1');
//////// }
//////// }
//////// }
////////}