mailwolt/app/Console/Commands/UpdateMailboxStats.php

824 lines
29 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\Console\Commands;
use App\Models\MailUser;
use App\Models\Setting;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
class UpdateMailboxStats extends Command
{
protected $signature = 'mail:update-stats {--user=}';
protected $description = 'Aktualisiert Quota & Nachrichtenzahl (Settings/Redis; ohne MailUser-DB-Felder).';
public function handle(): int
{
$log = Log::channel('mailstats');
$onlyUser = trim((string)$this->option('user')) ?: null;
$started = microtime(true);
$q = MailUser::query()
->with('domain:id,domain,is_system')
->where('is_active', true)
->where('is_system', false) // keine System-Mailboxen
->whereHas('domain', fn($d) => $d->where('is_system', false)); // keine Systemdomains
if ($onlyUser) {
$q->where('email', $onlyUser);
}
$users = $q->get();
$log->info('mail:update-stats START', [
'only' => $onlyUser,
'users' => $users->count(),
]);
if ($users->isEmpty()) {
$this->warn('Keine passenden Mailboxen gefunden.');
$log->info('mail:update-stats DONE (no users)', ['ms' => (int)((microtime(true) - $started) * 1000)]);
return self::SUCCESS;
}
$changed = 0;
$checked = 0;
foreach ($users as $u) {
$checked++;
// Email robust ermitteln
$raw = (string)($u->getRawOriginal('email') ?? '');
$email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? null);
if (!is_string($email) || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
// nur debug vermeidet Spam
$log->debug('skip invalid email', ['user_id' => $u->id, 'raw' => $raw, 'computed' => $email]);
continue;
}
[$local, $domain] = explode('@', $email, 2);
$maildir = "/var/mail/vhosts/{$domain}/{$local}";
// Größe (Dateisystem)
$usedBytes = 0;
if (is_dir($maildir)) {
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS)
);
foreach ($it as $f) {
if ($f->isFile()) {
$usedBytes += $f->getSize();
}
}
}
// Nachrichten zählen
$messageCount = $this->countViaDoveadm($email);
if ($messageCount === null) {
$messageCount = $this->countViaFilesystem($maildir);
}
// Nur bei Änderungen persistieren + loggen
$key = "mailbox.{$email}";
$prev = Setting::get($key, null) ?: [];
$new = [
'used_bytes' => (int)$usedBytes,
'message_count' => (int)$messageCount,
'updated_at' => now()->toDateTimeString(),
];
if (
!is_array($prev) ||
($prev['used_bytes'] ?? null) !== $new['used_bytes'] ||
($prev['message_count'] ?? null) !== $new['message_count']
) {
Setting::set($key, $new);
$changed++;
// kurze, nützliche Info nur bei Änderung
$this->line(sprintf("%-35s %7.1f MiB %5d msgs",
$email, $usedBytes / 1024 / 1024, $messageCount));
$log->info('updated', [
'email' => $email,
'used_bytes' => $new['used_bytes'],
'message_count' => $new['message_count'],
]);
} else {
// keine Änderung → kein Info-Log
$log->debug('unchanged', ['email' => $email]);
}
}
$ms = (int)((microtime(true) - $started) * 1000);
$log->info('mail:update-stats DONE', [
'checked' => $checked,
'changed' => $changed,
'ms' => $ms,
]);
$this->info('Mailbox-Statistiken aktualisiert.');
return self::SUCCESS;
}
protected function countViaDoveadm(string $email): ?int
{
$cmd = "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u "
. escapeshellarg($email) . " messages INBOX 2>&1";
$out = [];
$rc = 0;
exec($cmd, $out, $rc);
if ($rc !== 0) return null;
foreach ($out as $line) {
if (preg_match('/^\s*INBOX\s+(\d+)\s*$/i', trim($line), $m)) {
return (int)$m[1];
}
}
return null;
}
protected function countViaFilesystem(string $maildir): int
{
$n = 0;
foreach (['cur', 'new'] as $sub) {
$dir = "{$maildir}/{$sub}";
if (!is_dir($dir)) continue;
$h = opendir($dir);
if (!$h) continue;
while (($fn = readdir($h)) !== false) {
if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue;
$n++;
}
closedir($h);
}
return $n;
}
}
//namespace App\Console\Commands;
//
//use App\Models\MailUser;
//use App\Models\Setting;
//use Illuminate\Console\Command;
//use Illuminate\Support\Facades\Log;
//use RecursiveDirectoryIterator;
//use RecursiveIteratorIterator;
//
//class UpdateMailboxStats extends Command
//{
// protected $signature = 'mail:update-stats {--user=}';
// protected $description = 'Aktualisiert Quota & Nachrichtenzahl (Settings/Redis; DB-frei)';
//
// public function handle(): int
// {
// Log::info('=== mail:update-stats START ===', ['userOpt' => $this->option('user')]);
//
// $q = MailUser::query()
// ->with('domain:id,domain')
// ->active();
//
// if ($only = trim((string)$this->option('user'))) {
// $q->byEmail($only);
// }
//
// $users = $q->get();
// if ($users->isEmpty()) {
// Log::warning('Keine passenden Mailboxen gefunden.');
// $this->warn('Keine passenden Mailboxen gefunden.');
// return self::SUCCESS;
// }
//
// foreach ($users as $u) {
// // 1) sichere E-Mail ermitteln: roher DB-Wert → Accessor → zusammengesetzt
// $raw = (string)($u->getRawOriginal('email') ?? '');
// $email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? '');
//
// if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', (string)$email)) {
// Log::warning('Ungültige effektive Adresse skip', [
// 'user_id' => $u->id, 'raw' => $raw, 'computed' => $email
// ]);
// continue;
// }
//
// [$local, $domain] = explode('@', $email, 2);
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
//
// // 2) Größe (Dateisystem)
// $usedBytes = 0;
// if (is_dir($maildir)) {
// $it = new RecursiveIteratorIterator(
// new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS)
// );
// foreach ($it as $f) {
// if ($f->isFile()) $usedBytes += $f->getSize();
// }
// }
//
// // 3) Message-Count prefer doveadm als vmail; Fallback: Files
// $messageCount = $this->countViaDoveadm($email);
// if ($messageCount === null) {
// $messageCount = $this->countViaFilesystem($maildir);
// }
//
// // 4) In Settings (→ Redis + DB-Backup) persistieren
// Setting::set("mailbox.{$email}", [
// 'used_bytes' => (int)$usedBytes,
// 'message_count' => (int)$messageCount,
// 'updated_at' => now()->toDateTimeString(),
// ]);
//
// $this->line(sprintf("%-35s %7.1f MiB %5d msgs",
// $email, $usedBytes / 1024 / 1024, $messageCount));
//
// Log::info('Mailbox-Stat OK', [
// 'email' => $email,
// 'maildir' => $maildir,
// 'used_bytes' => $usedBytes,
// 'message_count' => $messageCount,
// ]);
// }
//
// Log::info('=== mail:update-stats DONE ===');
// $this->info('Mailbox-Statistiken aktualisiert.');
// return self::SUCCESS;
// }
//
// protected function countViaDoveadm(string $email): ?int
// {
// $cmd = "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u "
// . escapeshellarg($email) . " messages INBOX 2>&1";
// $out = [];
// $rc = 0;
// exec($cmd, $out, $rc);
// if ($rc !== 0) return null;
//
// foreach ($out as $line) {
// if (preg_match('/^\s*INBOX\s+(\d+)\s*$/i', trim($line), $m)) {
// return (int)$m[1];
// }
// }
// return null;
// }
//
// protected function countViaFilesystem(string $maildir): int
// {
// $n = 0;
// foreach (['cur', 'new'] as $sub) {
// $dir = "{$maildir}/{$sub}";
// if (!is_dir($dir)) continue;
// $h = opendir($dir);
// if (!$h) continue;
// while (($fn = readdir($h)) !== false) {
// if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue;
// $n++;
// }
// closedir($h);
// }
// return $n;
// }
//}
//namespace App\Console\Commands;
//
//use Illuminate\Console\Command;
//use Illuminate\Support\Facades\Log;
//use App\Models\MailUser;
//use App\Models\Setting;
//use RecursiveDirectoryIterator;
//use RecursiveIteratorIterator;
//class UpdateMailboxStats extends Command
//{
// protected $signature = 'mail:update-stats {--user=}';
// protected $description = 'Aktualisiert Mailquota und Nachrichtenzahl';
//
// public function handle(): int
// {
// Log::info('=== mail:update-stats START ===', ['userOpt' => $this->option('user')]);
//
// $q = MailUser::query()
// ->with('domain:id,domain') // <— Domain fürs Fallback laden
// ->where('is_active', true);
//
// if ($only = trim((string)$this->option('user'))) {
// $q->where('email', $only);
// } else {
// $q->whereNotNull('email')->where('email', 'like', '%@%');
// }
//
// $users = $q->get();
// if ($users->isEmpty()) {
// Log::warning('Keine passenden Mailboxen gefunden.');
// $this->warn('Keine passenden Mailboxen gefunden.');
// return self::SUCCESS;
// }
//
// foreach ($users as $u) {
// // 1) rohen DB-Wert nehmen (ohne Accessor/Casts)
// $rawEmail = (string) $u->getRawOriginal('email');
//
// // 2) Fallback aus localpart + domain, falls roher Wert leer
// $fallback = ($u->localpart ?? '') . '@' . ($u->domain->domain ?? '');
// $email = trim($rawEmail !== '' ? $rawEmail : $fallback);
//
// if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
// Log::warning('Ungültige effektive Adresse skip', [
// 'raw' => $rawEmail,
// 'fallback' => $fallback,
// 'got' => $email,
// 'user_id' => $u->id,
// ]);
// continue;
// }
//
// [$local, $domain] = explode('@', $email, 2);
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
//
// // Größe (Dateisystem)
// $usedBytes = 0;
// if (is_dir($maildir)) {
// $it = new RecursiveIteratorIterator(
// new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS)
// );
// foreach ($it as $f) {
// if ($f->isFile()) $usedBytes += $f->getSize();
// }
// }
//
// // Zähler via doveadm (als vmail), Fallback: Files zählen
// $messageCount = $this->countViaDoveadm($email);
// if ($messageCount === null) {
// $messageCount = $this->countViaFilesystem($maildir);
// }
//
// // Optional zusätzlich cachen (UI liest das bereits)
// Setting::set("mailbox.{$email}", [
// 'used_bytes' => (int)$usedBytes,
// 'message_count' => (int)$messageCount,
// 'updated_at' => now()->toDateTimeString(),
// ]);
//
// $this->line(sprintf("%-35s %7.1f MiB %5d msgs", $email, $usedBytes/1024/1024, $messageCount));
// Log::info('Mailbox-Stat OK', compact('email','maildir','usedBytes','messageCount'));
// }
//
// Log::info('=== mail:update-stats DONE ===');
// $this->info('Mailbox-Statistiken aktualisiert.');
// return self::SUCCESS;
// }
//
// protected function countViaDoveadm(string $email): ?int
// {
// $cmd = "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u " . escapeshellarg($email) . " messages INBOX 2>&1";
// $out = []; $rc = 0;
// exec($cmd, $out, $rc);
// if ($rc !== 0) return null;
// foreach ($out as $line) {
// if (preg_match('/^\s*INBOX\s+(\d+)\s*$/i', trim($line), $m)) return (int)$m[1];
// }
// return null;
// }
//
// protected function countViaFilesystem(string $maildir): int
// {
// $n = 0;
// foreach (['cur','new'] as $sub) {
// $dir = "{$maildir}/{$sub}";
// if (!is_dir($dir)) continue;
// $h = opendir($dir);
// if (!$h) continue;
// while (($fn = readdir($h)) !== false) {
// if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue;
// $n++;
// }
// closedir($h);
// }
// return $n;
// }
//}
//namespace App\Console\Commands;
//
//use App\Models\MailUser;
//use App\Models\Setting;
//use Illuminate\Console\Command;
//use Illuminate\Support\Facades\Log;
//use RecursiveDirectoryIterator;
//use RecursiveIteratorIterator;
//
//class UpdateMailboxStats extends Command
//{
// protected $signature = 'mail:update-stats {--user=}';
// protected $description = 'Aktualisiert Mailquota und Nachrichtenzahl für alle Mailboxen (oder einen Benutzer)';
//
// public function handle(): int
// {
// Log::channel('mailstats')->info('=== mail:update-stats START ===', [
// 'userOpt' => $this->option('user')
// ]);
//
// $q = MailUser::query()
// ->where('is_active', true)
// ->whereNotNull('email')
// ->where('email', 'like', '%@%');
//
// if ($email = trim((string)$this->option('user'))) {
// $q->where('email', $email);
// }
//
// $users = $q->get();
//
// if ($users->isEmpty()) {
// Log::channel('mailstats')->warning('Keine passenden Mailboxen gefunden.');
// $this->warn('Keine passenden Mailboxen gefunden.');
// return self::SUCCESS;
// }
//
// foreach ($users as $u) {
// $email = trim((string)$u->email);
//
// if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
// Log::channel('mailstats')->warning('Überspringe ungültige Adresse', ['email' => $email]);
// continue;
// }
//
// [$local, $domain] = explode('@', $email, 2);
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
//
// // 1) Größe berechnen (Dateisystem)
// $usedBytes = 0;
// if (is_dir($maildir)) {
// $it = new RecursiveIteratorIterator(
// new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS)
// );
// foreach ($it as $f) {
// if ($f->isFile()) $usedBytes += $f->getSize();
// }
// }
//
// // 2) Nachrichten zählen bevorzugt via doveadm als vmail
// $messageCount = $this->countViaDoveadm($email);
// if ($messageCount === null) {
// $messageCount = $this->countViaFilesystem($maildir);
// }
//
// // optional: in Settings/Redis cachen (für UI)
// Setting::set("mailbox.{$email}", [
// 'used_bytes' => (int)$usedBytes,
// 'message_count' => (int)$messageCount,
// 'updated_at' => now()->toDateTimeString(),
// ]);
//
// $line = sprintf("%-35s %7.1f MiB %5d msgs", $email, $usedBytes / 1024 / 1024, $messageCount);
// $this->line($line);
// Log::channel('mailstats')->info('OK', [
// 'email' => $email,
// 'maildir' => $maildir,
// 'used_bytes' => $usedBytes,
// 'message_count' => $messageCount,
// ]);
// }
//
// Log::channel('mailstats')->info('=== mail:update-stats DONE ===');
// $this->info('Mailbox-Statistiken aktualisiert.');
// return self::SUCCESS;
// }
//
// protected function countViaDoveadm(string $email): ?int
// {
// $cmd = "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u " . escapeshellarg($email) . " messages INBOX 2>&1";
// $out = [];
// $rc = 0;
// exec($cmd, $out, $rc);
//
// Log::channel('mailstats')->debug('doveadm exec', ['cmd' => $cmd, 'rc' => $rc, 'out' => $out]);
//
// if ($rc !== 0) return null;
//
// // Erwartet: "mailbox\tmessages\nINBOX\t<number>"
// foreach ($out as $line) {
// if (preg_match('/^\s*INBOX\s+(\d+)\s*$/i', trim($line), $m)) {
// return (int)$m[1];
// }
// }
// return null;
// }
//
// protected function countViaFilesystem(string $maildir): int
// {
// $n = 0;
// foreach (['cur', 'new'] as $sub) {
// $dir = "{$maildir}/{$sub}";
// if (!is_dir($dir)) continue;
// $dh = opendir($dir);
// if (!$dh) continue;
// while (($fn = readdir($dh)) !== false) {
// if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue;
// $n++;
// }
// closedir($dh);
// }
// Log::channel('mailstats')->debug('fs count', ['maildir' => $maildir, 'count' => $n]);
// return $n;
// }
//}
//namespace App\Console\Commands;
//
//use App\Models\MailUser;
//use App\Models\Setting;
//use Illuminate\Console\Command;
//use Illuminate\Support\Facades\Log;
//use RecursiveDirectoryIterator;
//use RecursiveIteratorIterator;
//use Symfony\Component\Process\Process;
//
//class UpdateMailboxStats extends Command
//{
// protected $signature = 'mail:update-stats {--user=}';
// protected $description = 'Aktualisiert Mailquota und Nachrichtenzahl für alle Mailboxen (oder einen Benutzer)';
//
// public function handle(): int
// {
// $log = Log::channel('mailstats');
// $log->info('=== mail:update-stats gestartet', [
// 'user_opt' => $this->option('user'),
// 'php_user' => get_current_user(),
// 'uid' => getmyuid(),
// ]);
//
// $q = MailUser::query()
// ->where('is_active', true)
// ->with(['domain:id,domain']);
//
// if ($one = trim((string)$this->option('user'))) {
// if (str_contains($one, '@')) {
// [$local, $dom] = explode('@', $one, 2);
// $q->where('localpart', $local)
// ->whereHas('domain', fn($d) => $d->where('domain', $dom));
// } else {
// $q->where('localpart', $one);
// }
// }
//
// $users = $q->get();
// $log->info('Gefundene Mailboxen', ['count' => $users->count(), 'ids' => $users->pluck('id')]);
//
// if ($users->isEmpty()) {
// $this->warn('Keine passenden Mailboxen gefunden.');
// $log->warning('Abbruch: keine Mailboxen gefunden');
// return self::SUCCESS;
// }
//
// foreach ($users as $u) {
// $local = trim((string)$u->localpart);
// $domain = optional($u->domain)->domain;
// $email = trim((string)($u->email ?? ''));
//
// if ($email === '' && $local !== '' && $domain) {
// $email = "{$local}@{$domain}";
// }
//
// if ($email === '' || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
// $log->warning('Überspringe: ungültige Adresse', ['id' => $u->id, 'local' => $local, 'domain' => $domain, 'email' => $u->email]);
// continue;
// }
//
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
// $log->info('Verarbeite Mailbox', ['id' => $u->id, 'email' => $email, 'maildir' => $maildir]);
//
// // 1) Größe berechnen
// $usedBytes = 0;
// if (is_dir($maildir)) {
// $it = new RecursiveIteratorIterator(
// new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS)
// );
// foreach ($it as $file) {
// if ($file->isFile()) {
// $usedBytes += $file->getSize();
// }
// }
// } else {
// $log->warning('Maildir nicht gefunden', ['path' => $maildir]);
// }
//
// // 2) Nachrichtenzahl
// $messageCount = $this->countViaDoveadm($email, $log);
// if ($messageCount === null) {
// $messageCount = $this->countViaFilesystem($maildir, $log);
// }
//
// // Cache (Settings/Redis)
// Setting::set("mailbox.{$email}", [
// 'used_bytes' => (int)$usedBytes,
// 'message_count' => (int)$messageCount,
// 'ts' => now()->toISOString(),
// ]);
//
// $this->line(sprintf("%-40s %7.1f MiB %5d msgs", $email, $usedBytes / 1024 / 1024, $messageCount));
// $log->info('Aktualisiert', [
// 'email' => $email,
// 'used_bytes' => $usedBytes,
// 'message_count' => $messageCount,
// ]);
// }
//
// $log->info('=== mail:update-stats fertig ===');
// $this->info('Mailbox-Statistiken aktualisiert.');
// return self::SUCCESS;
// }
//
// protected function countViaDoveadm(string $email, $log): ?int
// {
// $cmd = ['sudo', '-n', '-u', 'vmail', '/usr/bin/doveadm', '-f', 'tab', 'mailbox', 'status', '-u', $email, 'messages', 'INBOX'];
// $p = new Process($cmd, null, ['LANG' => 'C']);
// $p->setTimeout(10);
//
// try {
// $log->debug('Starte doveadm', ['cmd' => implode(' ', $cmd)]);
// $p->run();
// $rc = $p->getExitCode();
// $out = trim($p->getOutput());
// $err = trim($p->getErrorOutput());
// $log->debug('doveadm Ergebnis', ['rc' => $rc, 'out' => $out, 'err' => $err]);
//
// if ($rc !== 0) return null;
//
// $lines = preg_split('/\R+/', $out);
// $last = end($lines);
// if (preg_match('/\t(\d+)$/', (string)$last, $m)) {
// return (int)$m[1];
// }
// return null;
// } catch (\Throwable $e) {
// $log->error('doveadm Exception', ['msg' => $e->getMessage()]);
// return null;
// }
// }
//
// protected function countViaFilesystem(string $maildir, $log): int
// {
// $count = 0;
// foreach (['cur', 'new'] as $sub) {
// $dir = "{$maildir}/{$sub}";
// if (is_dir($dir) && ($dh = @opendir($dir))) {
// while (false !== ($fn = readdir($dh))) {
// if ($fn !== '.' && $fn !== '..' && $fn[0] !== '.') {
// $count++;
// }
// }
// closedir($dh);
// } else {
// $log->debug('Ordner fehlt/leer', ['path' => $dir]);
// }
// }
// return $count;
// }
//}
//
//namespace App\Console\Commands;
//
//use App\Models\MailUser;
//use App\Models\Setting;
//use Illuminate\Console\Command;
//use RecursiveDirectoryIterator;
//use RecursiveIteratorIterator;
//
//class UpdateMailboxStats extends Command
//{
// protected $signature = 'mail:update-stats {--user=}';
// protected $description = 'Aktualisiert Mailquota und Nachrichtenzahl für alle Mailboxen (oder einen Benutzer)';
//
// public function handle(): int
// {
// $q = MailUser::query()
// ->where('is_active', true)
// ->whereNotNull('email')
// ->where('email', 'like', '%@%');
//
// if ($email = trim((string)$this->option('user'))) {
// $q->where('email', $email);
// }
//
// $users = $q->get();
// if ($users->isEmpty()) {
// $this->warn('Keine passenden Mailboxen gefunden.');
// return self::SUCCESS;
// }
//
// foreach ($users as $u) {
// $email = trim($u->email);
// if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
// // ungültig → überspringen
// continue;
// }
//
// [$local, $domain] = explode('@', $email, 2);
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
//
// // 1) Größe (rekursiv, ohne `du`)
// $usedBytes = 0;
// if (is_dir($maildir)) {
// $it = new RecursiveIteratorIterator(
// new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS)
// );
// foreach ($it as $file) {
// if ($file->isFile()) {
// $usedBytes += $file->getSize();
// }
// }
// }
//
// // 2) Nachrichtenzahl bevorzugt via doveadm (sudo -u vmail), Fallback: Dateien zählen
// $messageCount = $this->messageCountViaDoveadm($email);
// if ($messageCount === null) {
// $messageCount = $this->messageCountViaFilesystem($maildir);
// }
//
// Setting::set("mailbox.{$email}", [
// 'used_bytes' => $usedBytes,
// 'message_count' => $messageCount,
// 'updated_at' => now()->toDateTimeString(),
// ]);
//
// $this->line(sprintf(
// "%-35s %7.1f MiB %5d Messages",
// $email,
// $usedBytes / 1024 / 1024,
// $messageCount
// ));
// }
//
// $this->info('Mailbox-Statistiken aktualisiert.');
// return self::SUCCESS;
// }
//
// /**
// * Holt die Anzahl der Nachrichten in INBOX über doveadm als vmail (sudo).
// * Gibt int bei Erfolg, oder null bei Fehlern zurück.
// */
// private function messageCountViaDoveadm(string $email): ?int
// {
// // bevorzugtes Format: tabellarisch → "INBOX\t123"
// $cmd = sprintf(
// "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u %s messages INBOX 2>/dev/null",
// escapeshellarg($email)
// );
// $out = trim((string) shell_exec($cmd));
//
// if ($out === '') {
// // Fallback auf normales Format: "messages=123"
// $cmd2 = sprintf(
// "sudo -n -u vmail /usr/bin/doveadm mailbox status -u %s messages INBOX 2>/dev/null",
// escapeshellarg($email)
// );
// $out = trim((string) shell_exec($cmd2));
// }
//
// if ($out === '') {
// return null;
// }
//
// // Match "INBOX<TAB>123" oder "messages=123"
// if (preg_match('/\t(\d+)\s*$/', $out, $m) || preg_match('/messages=(\d+)/', $out, $m)) {
// return (int) $m[1];
// }
//
// return null;
// }
//
// /**
// * Fallback: Zählt Dateien in cur/ + new/ (Maildir).
// */
// private function messageCountViaFilesystem(string $maildir): int
// {
// $count = 0;
// foreach (['cur', 'new'] as $sub) {
// $dir = "{$maildir}/{$sub}";
// if (is_dir($dir) && is_readable($dir)) {
// $dh = opendir($dir);
// if ($dh) {
// while (($fn = readdir($dh)) !== false) {
// if ($fn !== '.' && $fn !== '..' && $fn[0] !== '.') {
// $count++;
// }
// }
// closedir($dh);
// }
// }
// }
// return $count;
// }
//}