Files
MeshVault/src/Indexer/SpaceChecker.php
T

47 lines
1.7 KiB
PHP

<?php
declare(strict_types=1);
namespace MeshVault\Indexer;
use Closure;
use InvalidArgumentException;
use Throwable;
/** Advisory preflight only: free space may change immediately after checking. */
final readonly class SpaceChecker
{
private Closure $freeSpace;
/** Margin is a fraction of payload bytes, not a fraction of disk capacity. */
public function __construct(private float $margin = 0.05, ?callable $freeSpace = null)
{
if (!is_finite($margin) || $margin < 0) {
throw new InvalidArgumentException('Space margin must be finite and nonnegative.');
}
$this->freeSpace = $freeSpace === null
? static fn (string $path): float|false => @disk_free_space($path)
: Closure::fromCallable($freeSpace);
}
/** Require ceil(bytes * (1 + margin)) available bytes on an existing destination directory. */
public function check(string $directory, int $bytes): void
{
if ($bytes < 0) {
throw new InvalidArgumentException('Payload size must be nonnegative.');
}
try {
$free = ($this->freeSpace)($directory);
} catch (Throwable $error) {
throw new ImportException('SPACE_CHECK_FAILED', 'Cannot query destination free space.', $error);
}
if ((!is_float($free) && !is_int($free)) || !is_finite((float) $free) || $free < 0) {
throw new ImportException('SPACE_CHECK_FAILED', 'Cannot determine destination free space.');
}
$required = ceil($bytes + $bytes * $this->margin);
if (!is_finite($required) || $free < $required) {
throw new ImportException('DISK_FULL', 'Insufficient free space including the safety margin.');
}
}
}