From fa4c36ea6ea2112e040078603434ff2d88bca008 Mon Sep 17 00:00:00 2001 From: IvanCraft623 Date: Thu, 20 Aug 2026 14:46:58 -0600 Subject: [PATCH 1/5] libasyncio: decouple compression from zstd --- .../FileOrDirectoryCompressTask.php | 25 +++-- .../FileOrDirectoryUncompressTask.php | 24 +++-- ...Compressor.php => RecursiveCompressor.php} | 72 ++++++++------ libraries/libasyncio/composer.json | 6 +- .../libasyncio/compression/Compression.php | 78 +++++++++++++++ .../compression/CompressionFormat.php | 77 +++++++++++++++ .../libasyncio/compression/Compressor.php | 46 +++++++++ libraries/libasyncio/compression/Deflate.php | 98 +++++++++++++++++++ libraries/libasyncio/compression/Gzip.php | 80 +++++++++++++++ libraries/libasyncio/compression/Zstd.php | 83 ++++++++++++++++ .../libasyncio/stub/LibDeflate/libdeflate.php | 23 +++++ 11 files changed, 565 insertions(+), 47 deletions(-) rename libraries/libasyncio/{ZstdRecursiveCompressor.php => RecursiveCompressor.php} (64%) create mode 100644 libraries/libasyncio/compression/Compression.php create mode 100644 libraries/libasyncio/compression/CompressionFormat.php create mode 100644 libraries/libasyncio/compression/Compressor.php create mode 100644 libraries/libasyncio/compression/Deflate.php create mode 100644 libraries/libasyncio/compression/Gzip.php create mode 100644 libraries/libasyncio/compression/Zstd.php create mode 100644 libraries/libasyncio/stub/LibDeflate/libdeflate.php diff --git a/libraries/libasyncio/FileOrDirectoryCompressTask.php b/libraries/libasyncio/FileOrDirectoryCompressTask.php index 7de459e..477bce4 100644 --- a/libraries/libasyncio/FileOrDirectoryCompressTask.php +++ b/libraries/libasyncio/FileOrDirectoryCompressTask.php @@ -23,6 +23,9 @@ namespace libasyncio; +use libasyncio\compression\Compression; +use libasyncio\compression\CompressionFormat; +use libasyncio\compression\Compressor; use pocketmine\Server; class FileOrDirectoryCompressTask extends FileOperationTask @@ -31,9 +34,11 @@ class FileOrDirectoryCompressTask extends FileOperationTask /** @var string */ private string $input; /** @var string */ - private $output; - /** @var int */ - private $compressionLevel; + private string $output; + /** @var int|null */ + private ?int $compressionLevel; + /** @var Compressor */ + private Compressor $compressor; /** * FileOrDirectoryCompressTask constructor. @@ -41,12 +46,14 @@ class FileOrDirectoryCompressTask extends FileOperationTask * @param string $input * @param string $output * @param callable $callable - * @param int $compressionLevel + * @param int|null $compressionLevel + * @param CompressionFormat|null $format */ - public function __construct(string $input, string $output, callable $callable, int $compressionLevel = ZstdRecursiveCompressor::COMPRESSION_LEVEL) + public function __construct(string $input, string $output, callable $callable, ?int $compressionLevel = null, ?CompressionFormat $format = null) { + $this->compressor = $format !== null ? Compression::get($format) : Compression::auto(); $this->input = $input; - $this->output = str_replace('.' . ZstdRecursiveCompressor::COMPRESSION_FORMAT, '', $output); + $this->output = ($outputFormat = CompressionFormat::fromPath($output)) !== null ? substr($output, 0, -strlen('.' . $outputFormat->getFileExtension())) : $output; $this->compressionLevel = $compressionLevel; parent::__construct($input, $callable); } @@ -57,12 +64,12 @@ public function __construct(string $input, string $output, callable $callable, i public function onRun(): void { parent::onRun(); - $this->setSuccess(ZstdRecursiveCompressor::compress($this->input, $this->output, $this->compressionLevel)); + $this->setSuccess(RecursiveCompressor::compress($this->input, $this->output, $this->compressionLevel, $this->compressor->getFormat())); } protected function checkSuccess(): void { - $outputLocation = $this->output . '.' . ZstdRecursiveCompressor::COMPRESSION_FORMAT; + $outputLocation = $this->output . '.' . $this->compressor->getFormat()->getFileExtension(); if ($this->getSuccess()) { Server::getInstance()->getLogger()->debug("Compressed directory/file {$this->input} to {$outputLocation}"); } else { @@ -70,4 +77,4 @@ protected function checkSuccess(): void } } -} \ No newline at end of file +} diff --git a/libraries/libasyncio/FileOrDirectoryUncompressTask.php b/libraries/libasyncio/FileOrDirectoryUncompressTask.php index c7c0885..82ede5d 100644 --- a/libraries/libasyncio/FileOrDirectoryUncompressTask.php +++ b/libraries/libasyncio/FileOrDirectoryUncompressTask.php @@ -23,26 +23,34 @@ namespace libasyncio; +use libasyncio\compression\Compression; +use libasyncio\compression\CompressionFormat; +use libasyncio\compression\Compressor; use pocketmine\Server; +use function str_ends_with; class FileOrDirectoryUncompressTask extends FileOperationTask { /** @var string */ - private $input; + private string $input; /** @var string */ private string $output; + /** @var Compressor */ + private Compressor $compressor; /** - * FileOrDirectoryCompressTask constructor. + * FileOrDirectoryUncompressTask constructor. * * @param string $input * @param string $output * @param callable $callable + * @param CompressionFormat|null $format */ - public function __construct(string $input, string $output, callable $callable) + public function __construct(string $input, string $output, callable $callable, ?CompressionFormat $format = null) { - $this->input = str_replace('.' . ZstdRecursiveCompressor::COMPRESSION_FORMAT, '', $input); + $this->compressor = $format !== null ? Compression::get($format) : Compression::fromPath($input); + $this->input = $input; $this->output = $output; parent::__construct($input, $callable); } @@ -53,12 +61,14 @@ public function __construct(string $input, string $output, callable $callable) public function onRun(): void { parent::onRun(); - $this->setSuccess(ZstdRecursiveCompressor::uncompress($this->input, $this->output)); + $this->setSuccess(RecursiveCompressor::uncompress($this->input, $this->output, $this->compressor->getFormat())); } protected function checkSuccess(): void { - $inputLocation = $this->input . '.' . ZstdRecursiveCompressor::COMPRESSION_FORMAT; + $extension = $this->compressor->getFormat()->getFileExtension(); + $inputLocation = str_ends_with($this->input, '.' . $extension) ? $this->input : $this->input . '.' . $extension; + if ($this->getSuccess()) { Server::getInstance()->getLogger()->debug("Uncompressed directory/file {$inputLocation} to {$this->output}"); } else { @@ -66,4 +76,4 @@ protected function checkSuccess(): void } } -} \ No newline at end of file +} diff --git a/libraries/libasyncio/ZstdRecursiveCompressor.php b/libraries/libasyncio/RecursiveCompressor.php similarity index 64% rename from libraries/libasyncio/ZstdRecursiveCompressor.php rename to libraries/libasyncio/RecursiveCompressor.php index aab594b..c91d9a0 100644 --- a/libraries/libasyncio/ZstdRecursiveCompressor.php +++ b/libraries/libasyncio/RecursiveCompressor.php @@ -24,23 +24,23 @@ namespace libasyncio; use GlobalLogger; +use libasyncio\compression\Compression; +use libasyncio\compression\CompressionFormat; +use libasyncio\compression\Compressor; use Phar; use PharData; use pocketmine\utils\Filesystem; use RuntimeException; use Throwable; use function is_dir; +use function is_file; use function mkdir; -use function sprintf; +use function str_ends_with; -class ZstdRecursiveCompressor +class RecursiveCompressor { - /** @var int */ - public const COMPRESSION_LEVEL = ZSTD_COMPRESS_LEVEL_MAX; - public const ARCHIVE_FORMAT = 'tar'; - public const COMPRESSION_FORMAT = 'ngzstd'; /** * Compress a directory. @@ -48,22 +48,18 @@ class ZstdRecursiveCompressor * like path. It's important you don't * use a file name for it. * - * Output format is COMPRESSION_FORMAT. + * Output format is the chosen compression format. * * @param string $input * @param string $output - * @param int $compressionLevel + * @param int|null $compressionLevel + * @param CompressionFormat|null $format * * @return bool */ - public static function compress(string $input, string $output, int $compressionLevel = self::COMPRESSION_LEVEL): bool + public static function compress(string $input, string $output, ?int $compressionLevel = null, ?CompressionFormat $format = null): bool { - if ($compressionLevel < ZSTD_COMPRESS_LEVEL_MIN || $compressionLevel > ZSTD_COMPRESS_LEVEL_MAX) { - throw new RuntimeException( - 'Compression level must cannot either lower than ' . ZSTD_COMPRESS_LEVEL_MIN . - ' or higher than ' . ZSTD_COMPRESS_LEVEL_MAX . ', ' . $compressionLevel . ' given' - ); - } + $compressor = self::resolveCompressor($format); $archive = new PharData($input . '.' . self::ARCHIVE_FORMAT); $archive->buildFromDirectory($input); @@ -73,12 +69,9 @@ public static function compress(string $input, string $output, int $compressionL throw new RuntimeException('Archive unreadable'); } - $compressedData = zstd_compress($data, $compressionLevel); - if (!is_string($compressedData)) { - throw new RuntimeException('Compression failed'); - } + $compressedData = $compressor->compress($data, $compressionLevel); - Filesystem::safeFilePutContents($output . '.' . self::COMPRESSION_FORMAT, $compressedData); + Filesystem::safeFilePutContents($output . '.' . $compressor->getFormat()->getFileExtension(), $compressedData); unset($archive); Phar::unlinkArchive($input . '.' . self::ARCHIVE_FORMAT); @@ -91,20 +84,27 @@ public static function compress(string $input, string $output, int $compressionL * like path. It's important you don't * use a file name for it. * - * Input format is COMPRESSION_FORMAT. + * Input format is the chosen compression format. * Output format is regular directory. * * @param string $input * @param string $output + * @param CompressionFormat|null $format * * @return bool */ - public static function uncompress(string $input, string $output): bool + public static function uncompress(string $input, string $output, ?CompressionFormat $format = null): bool { - $input .= '.' . self::COMPRESSION_FORMAT; + $compressor = self::resolveCompressor($format, $input); + + $extension = $compressor->getFormat()->getFileExtension(); + if (!str_ends_with($input, '.' . $extension)) { + $input .= '.' . $extension; + } + if (!is_file($input)) { throw new RuntimeException( - 'That file is not of type ' . self::COMPRESSION_FORMAT . ', cannot uncompress' + 'That file is not of type ' . $extension . ', cannot uncompress' ); } @@ -113,17 +113,14 @@ public static function uncompress(string $input, string $output): bool throw new RuntimeException('Compressed file unreadable'); } - $data = zstd_uncompress($compressedData); - if (!is_string($data)) { - throw new RuntimeException('Uncompression failed.'); - } + $data = $compressor->decompress($compressedData); Filesystem::safeFilePutContents($output . '.' . self::ARCHIVE_FORMAT, $data); $archive = new PharData($output . '.' . self::ARCHIVE_FORMAT); try { if (!is_dir($output) && !mkdir($output)) { - throw new RuntimeException(sprintf('Directory "%s" was not created', $output)); + throw new RuntimeException('Directory "' . $output . '" was not created'); } } catch (Throwable $exception) { GlobalLogger::get()->critical("Unhandled exception from a method that should never throw anything."); @@ -138,4 +135,19 @@ public static function uncompress(string $input, string $output): bool return true; } -} \ No newline at end of file + + /** + * @param CompressionFormat|null $format + * @param string|null $path + * + * @return Compressor + */ + private static function resolveCompressor(?CompressionFormat $format, ?string $path = null): Compressor + { + if ($format !== null) { + return Compression::get($format); + } + + return $path !== null ? Compression::fromPath($path) : Compression::auto(); + } +} diff --git a/libraries/libasyncio/composer.json b/libraries/libasyncio/composer.json index b9181e3..92aa2b5 100644 --- a/libraries/libasyncio/composer.json +++ b/libraries/libasyncio/composer.json @@ -5,9 +5,13 @@ "type": "project", "version": "dev-stable", "require": { - "php": "^8.0", + "php": "^8.1", "ext-igbinary": "*" }, + "suggest": { + "ext-zstd": "Required for ZSTD compression format", + "ext-libdeflate": "Faster DEFLATE compression" + }, "require-dev": { "phpstan/phpstan": "2.1.29", "nethergamesmc/pocketmine-mp": "dev-stable", diff --git a/libraries/libasyncio/compression/Compression.php b/libraries/libasyncio/compression/Compression.php new file mode 100644 index 0000000..1dc1b8e --- /dev/null +++ b/libraries/libasyncio/compression/Compression.php @@ -0,0 +1,78 @@ +getRequiredPHPExtension(); + if ($required !== null && !extension_loaded($required)) { + throw new InvalidArgumentException( + 'Compression format ' . $format->name . ' requires the "' . $required . '" extension, which is not loaded' + ); + } + + return match ($format) { + CompressionFormat::ZSTD => new Zstd(), + CompressionFormat::DEFLATE => new Deflate(), + CompressionFormat::GZIP => new Gzip(), + }; + } + + /** + * @return Compressor + */ + public static function auto(): Compressor + { + if (extension_loaded('zstd')) { + return new Zstd(); + } + + if (extension_loaded('libdeflate')) { + return new Deflate(); + } + + return new Gzip(); + } + + /** + * @param string $path + * @return Compressor + */ + public static function fromPath(string $path): Compressor + { + $format = CompressionFormat::fromPath($path); + + return $format !== null ? self::get($format) : self::auto(); + } +} diff --git a/libraries/libasyncio/compression/CompressionFormat.php b/libraries/libasyncio/compression/CompressionFormat.php new file mode 100644 index 0000000..ab535fd --- /dev/null +++ b/libraries/libasyncio/compression/CompressionFormat.php @@ -0,0 +1,77 @@ + 'ngzstd', + self::DEFLATE => 'ngdeflate', + self::GZIP => 'nggzip', + }; + } + + /** + * Name of the PHP extension required, or null when self-contained via core zlib. + * + * @return string|null + */ + public function getRequiredPHPExtension(): ?string + { + return match ($this) { + self::ZSTD => 'zstd', + self::DEFLATE => null, + self::GZIP => null, + }; + } + + /** + * @param string $path + * @return self|null + */ + public static function fromPath(string $path): ?self + { + foreach (self::cases() as $format) { + if (str_ends_with($path, '.' . $format->getFileExtension())) { + return $format; + } + } + + return null; + } +} diff --git a/libraries/libasyncio/compression/Compressor.php b/libraries/libasyncio/compression/Compressor.php new file mode 100644 index 0000000..3fe45d7 --- /dev/null +++ b/libraries/libasyncio/compression/Compressor.php @@ -0,0 +1,46 @@ + self::LEVEL_MAX) { + throw new InvalidArgumentException( + 'Compression level must be between ' . self::LEVEL_MIN . ' and ' . self::LEVEL_MAX . ', ' . $level . ' given' + ); + } + + if (extension_loaded('libdeflate')) { + return libdeflate_deflate_compress($data, $level); + } + + $result = zlib_encode($data, ZLIB_ENCODING_RAW, $level > 9 ? 9 : $level); + if (!is_string($result)) { + throw new RuntimeException('Compression failed'); + } + + return $result; + } + + /** + * @param string $data + * @return string + */ + public function decompress(string $data): string + { + $context = inflate_init(ZLIB_ENCODING_RAW); + $result = inflate_add($context, $data, ZLIB_FINISH); + if (!is_string($result)) { + throw new RuntimeException('Uncompression failed'); + } + + return $result; + } + + /** + * @return CompressionFormat + */ + public function getFormat(): CompressionFormat + { + return CompressionFormat::DEFLATE; + } +} diff --git a/libraries/libasyncio/compression/Gzip.php b/libraries/libasyncio/compression/Gzip.php new file mode 100644 index 0000000..2ee708f --- /dev/null +++ b/libraries/libasyncio/compression/Gzip.php @@ -0,0 +1,80 @@ + self::LEVEL_MAX)) { + throw new InvalidArgumentException( + 'Compression level must be between ' . self::LEVEL_MIN . ' and ' . self::LEVEL_MAX . ', ' . $level . ' given' + ); + } + + $result = gzencode($data, $level ?? -1); + if (!is_string($result)) { + throw new RuntimeException('Compression failed'); + } + + return $result; + } + + /** + * @param string $data + * @return string + */ + public function decompress(string $data): string + { + $result = gzdecode($data); + if (!is_string($result)) { + throw new RuntimeException('Uncompression failed'); + } + + return $result; + } + + /** + * @return CompressionFormat + */ + public function getFormat(): CompressionFormat + { + return CompressionFormat::GZIP; + } +} diff --git a/libraries/libasyncio/compression/Zstd.php b/libraries/libasyncio/compression/Zstd.php new file mode 100644 index 0000000..4bb125e --- /dev/null +++ b/libraries/libasyncio/compression/Zstd.php @@ -0,0 +1,83 @@ + self::LEVEL_MAX) { + throw new InvalidArgumentException( + 'Compression level must be between ' . self::LEVEL_MIN . ' and ' . self::LEVEL_MAX . ', ' . $level . ' given' + ); + } + + $result = zstd_compress($data, $level); + if (!is_string($result)) { + throw new RuntimeException('Compression failed'); + } + + return $result; + } + + /** + * @param string $data + * @return string + */ + public function decompress(string $data): string + { + $result = zstd_uncompress($data); + if (!is_string($result)) { + throw new RuntimeException('Uncompression failed'); + } + + return $result; + } + + /** + * @return CompressionFormat + */ + public function getFormat(): CompressionFormat + { + return CompressionFormat::ZSTD; + } +} diff --git a/libraries/libasyncio/stub/LibDeflate/libdeflate.php b/libraries/libasyncio/stub/LibDeflate/libdeflate.php new file mode 100644 index 0000000..1921e45 --- /dev/null +++ b/libraries/libasyncio/stub/LibDeflate/libdeflate.php @@ -0,0 +1,23 @@ + Date: Thu, 20 Aug 2026 14:59:04 -0600 Subject: [PATCH 2/5] ... --- libraries/libasyncio/compression/Deflate.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/libasyncio/compression/Deflate.php b/libraries/libasyncio/compression/Deflate.php index 2e21171..ed86e26 100644 --- a/libraries/libasyncio/compression/Deflate.php +++ b/libraries/libasyncio/compression/Deflate.php @@ -80,6 +80,10 @@ public function compress(string $data, ?int $level = null): string public function decompress(string $data): string { $context = inflate_init(ZLIB_ENCODING_RAW); + if ($context === false) { + throw new RuntimeException('Failed to initialize inflate context'); + } + $result = inflate_add($context, $data, ZLIB_FINISH); if (!is_string($result)) { throw new RuntimeException('Uncompression failed'); From c4e28589c35490f18a8024358f734510bb735d44 Mon Sep 17 00:00:00 2001 From: IvanCraft623 Date: Thu, 20 Aug 2026 21:44:46 -0600 Subject: [PATCH 3/5] Replace getRequiredPHPExtension() with isCompatible() --- libraries/libasyncio/compression/Compression.php | 7 ++----- .../libasyncio/compression/CompressionFormat.php | 13 +++++++------ libraries/libasyncio/compression/Deflate.php | 5 +++++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/libraries/libasyncio/compression/Compression.php b/libraries/libasyncio/compression/Compression.php index 1dc1b8e..707f256 100644 --- a/libraries/libasyncio/compression/Compression.php +++ b/libraries/libasyncio/compression/Compression.php @@ -35,11 +35,8 @@ final class Compression */ public static function get(CompressionFormat $format): Compressor { - $required = $format->getRequiredPHPExtension(); - if ($required !== null && !extension_loaded($required)) { - throw new InvalidArgumentException( - 'Compression format ' . $format->name . ' requires the "' . $required . '" extension, which is not loaded' - ); + if (!$format->isCompatible()) { + throw new InvalidArgumentException('Compression format ' . $format->name . ' is not compatible'); } return match ($format) { diff --git a/libraries/libasyncio/compression/CompressionFormat.php b/libraries/libasyncio/compression/CompressionFormat.php index ab535fd..5941a40 100644 --- a/libraries/libasyncio/compression/CompressionFormat.php +++ b/libraries/libasyncio/compression/CompressionFormat.php @@ -23,6 +23,7 @@ namespace libasyncio\compression; +use function extension_loaded; use function str_ends_with; enum CompressionFormat @@ -47,16 +48,16 @@ public function getFileExtension(): string } /** - * Name of the PHP extension required, or null when self-contained via core zlib. + * Whether this format is compatible and can be used. * - * @return string|null + * @return bool */ - public function getRequiredPHPExtension(): ?string + public function isCompatible(): bool { return match ($this) { - self::ZSTD => 'zstd', - self::DEFLATE => null, - self::GZIP => null, + self::ZSTD => extension_loaded('zstd'), + self::DEFLATE => extension_loaded('libdeflate') || extension_loaded('zlib'), + self::GZIP => extension_loaded('zlib'), }; } diff --git a/libraries/libasyncio/compression/Deflate.php b/libraries/libasyncio/compression/Deflate.php index ed86e26..8730fc1 100644 --- a/libraries/libasyncio/compression/Deflate.php +++ b/libraries/libasyncio/compression/Deflate.php @@ -65,6 +65,11 @@ public function compress(string $data, ?int $level = null): string return libdeflate_deflate_compress($data, $level); } + // Fallback to core zlib when ext-libdeflate is not available. + // pmmp/ext-libdeflate only exposes compression bindings and may not be + // installed; zlib_encode with ZLIB_ENCODING_RAW produces compatible + // raw DEFLATE output. Level is clamped to 9 as zlib supports 0-9 + // while libdeflate supports 0-12. $result = zlib_encode($data, ZLIB_ENCODING_RAW, $level > 9 ? 9 : $level); if (!is_string($result)) { throw new RuntimeException('Compression failed'); From 1ed09cc50ec266ba81dc48271874c414980ef4f5 Mon Sep 17 00:00:00 2001 From: IvanCraft623 Date: Thu, 20 Aug 2026 22:40:59 -0600 Subject: [PATCH 4/5] Merge Compression class into CompressionFormat enum --- .../FileOrDirectoryCompressTask.php | 8 +- .../FileOrDirectoryUncompressTask.php | 8 +- libraries/libasyncio/RecursiveCompressor.php | 5 +- .../libasyncio/compression/Compression.php | 75 ------------------- .../compression/CompressionFormat.php | 31 ++++++++ libraries/libasyncio/compression/Deflate.php | 2 +- 6 files changed, 46 insertions(+), 83 deletions(-) delete mode 100644 libraries/libasyncio/compression/Compression.php diff --git a/libraries/libasyncio/FileOrDirectoryCompressTask.php b/libraries/libasyncio/FileOrDirectoryCompressTask.php index 477bce4..6ef2de2 100644 --- a/libraries/libasyncio/FileOrDirectoryCompressTask.php +++ b/libraries/libasyncio/FileOrDirectoryCompressTask.php @@ -23,7 +23,7 @@ namespace libasyncio; -use libasyncio\compression\Compression; +use InvalidArgumentException; use libasyncio\compression\CompressionFormat; use libasyncio\compression\Compressor; use pocketmine\Server; @@ -51,7 +51,11 @@ class FileOrDirectoryCompressTask extends FileOperationTask */ public function __construct(string $input, string $output, callable $callable, ?int $compressionLevel = null, ?CompressionFormat $format = null) { - $this->compressor = $format !== null ? Compression::get($format) : Compression::auto(); + if ($format !== null && !$format->isCompatible()) { + throw new InvalidArgumentException('Compression format ' . $format->name . ' is not compatible'); + } + + $this->compressor = ($format ?? CompressionFormat::auto())->getCompressor(); $this->input = $input; $this->output = ($outputFormat = CompressionFormat::fromPath($output)) !== null ? substr($output, 0, -strlen('.' . $outputFormat->getFileExtension())) : $output; $this->compressionLevel = $compressionLevel; diff --git a/libraries/libasyncio/FileOrDirectoryUncompressTask.php b/libraries/libasyncio/FileOrDirectoryUncompressTask.php index 82ede5d..694ec71 100644 --- a/libraries/libasyncio/FileOrDirectoryUncompressTask.php +++ b/libraries/libasyncio/FileOrDirectoryUncompressTask.php @@ -23,7 +23,7 @@ namespace libasyncio; -use libasyncio\compression\Compression; +use InvalidArgumentException; use libasyncio\compression\CompressionFormat; use libasyncio\compression\Compressor; use pocketmine\Server; @@ -49,7 +49,11 @@ class FileOrDirectoryUncompressTask extends FileOperationTask */ public function __construct(string $input, string $output, callable $callable, ?CompressionFormat $format = null) { - $this->compressor = $format !== null ? Compression::get($format) : Compression::fromPath($input); + if ($format !== null && !$format->isCompatible()) { + throw new InvalidArgumentException('Compression format ' . $format->name . ' is not compatible'); + } + + $this->compressor = ($format ?? CompressionFormat::fromPath($input) ?? CompressionFormat::auto())->getCompressor(); $this->input = $input; $this->output = $output; parent::__construct($input, $callable); diff --git a/libraries/libasyncio/RecursiveCompressor.php b/libraries/libasyncio/RecursiveCompressor.php index c91d9a0..eb2f3fd 100644 --- a/libraries/libasyncio/RecursiveCompressor.php +++ b/libraries/libasyncio/RecursiveCompressor.php @@ -24,7 +24,6 @@ namespace libasyncio; use GlobalLogger; -use libasyncio\compression\Compression; use libasyncio\compression\CompressionFormat; use libasyncio\compression\Compressor; use Phar; @@ -145,9 +144,9 @@ public static function uncompress(string $input, string $output, ?CompressionFor private static function resolveCompressor(?CompressionFormat $format, ?string $path = null): Compressor { if ($format !== null) { - return Compression::get($format); + return $format->getCompressor(); } - return $path !== null ? Compression::fromPath($path) : Compression::auto(); + return ($path !== null ? (CompressionFormat::fromPath($path) ?? CompressionFormat::auto()) : CompressionFormat::auto())->getCompressor(); } } diff --git a/libraries/libasyncio/compression/Compression.php b/libraries/libasyncio/compression/Compression.php deleted file mode 100644 index 707f256..0000000 --- a/libraries/libasyncio/compression/Compression.php +++ /dev/null @@ -1,75 +0,0 @@ -isCompatible()) { - throw new InvalidArgumentException('Compression format ' . $format->name . ' is not compatible'); - } - - return match ($format) { - CompressionFormat::ZSTD => new Zstd(), - CompressionFormat::DEFLATE => new Deflate(), - CompressionFormat::GZIP => new Gzip(), - }; - } - - /** - * @return Compressor - */ - public static function auto(): Compressor - { - if (extension_loaded('zstd')) { - return new Zstd(); - } - - if (extension_loaded('libdeflate')) { - return new Deflate(); - } - - return new Gzip(); - } - - /** - * @param string $path - * @return Compressor - */ - public static function fromPath(string $path): Compressor - { - $format = CompressionFormat::fromPath($path); - - return $format !== null ? self::get($format) : self::auto(); - } -} diff --git a/libraries/libasyncio/compression/CompressionFormat.php b/libraries/libasyncio/compression/CompressionFormat.php index 5941a40..3bc9fad 100644 --- a/libraries/libasyncio/compression/CompressionFormat.php +++ b/libraries/libasyncio/compression/CompressionFormat.php @@ -23,9 +23,13 @@ namespace libasyncio\compression; +use RuntimeException; use function extension_loaded; use function str_ends_with; +/** + * Order of cases defines priority for {@link #auto()}. The first compatible format wins. + */ enum CompressionFormat { @@ -61,6 +65,18 @@ public function isCompatible(): bool }; } + /** + * @return Compressor + */ + public function getCompressor(): Compressor + { + return match ($this) { + self::ZSTD => new Zstd(), + self::DEFLATE => new Deflate(), + self::GZIP => new Gzip(), + }; + } + /** * @param string $path * @return self|null @@ -75,4 +91,19 @@ public static function fromPath(string $path): ?self return null; } + + /** + * @return self + * @throws RuntimeException if no compatible format is found + */ + public static function auto(): self + { + foreach (self::cases() as $format) { + if ($format->isCompatible()) { + return $format; + } + } + + throw new RuntimeException('No compatible compression format found'); + } } diff --git a/libraries/libasyncio/compression/Deflate.php b/libraries/libasyncio/compression/Deflate.php index 8730fc1..69880a3 100644 --- a/libraries/libasyncio/compression/Deflate.php +++ b/libraries/libasyncio/compression/Deflate.php @@ -65,7 +65,7 @@ public function compress(string $data, ?int $level = null): string return libdeflate_deflate_compress($data, $level); } - // Fallback to core zlib when ext-libdeflate is not available. + // TODO: HACK! Fallback to core zlib when ext-libdeflate is not available. // pmmp/ext-libdeflate only exposes compression bindings and may not be // installed; zlib_encode with ZLIB_ENCODING_RAW produces compatible // raw DEFLATE output. Level is clamped to 9 as zlib supports 0-9 From d2ebd394b51d0715a2e6eda87594eaa77c0ef70d Mon Sep 17 00:00:00 2001 From: IvanCraft623 Date: Thu, 20 Aug 2026 22:52:27 -0600 Subject: [PATCH 5/5] lib design prefers GobalLogs instead of propagating exceptions --- libraries/libasyncio/FileOrDirectoryCompressTask.php | 10 +++++++++- libraries/libasyncio/FileOrDirectoryUncompressTask.php | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libraries/libasyncio/FileOrDirectoryCompressTask.php b/libraries/libasyncio/FileOrDirectoryCompressTask.php index 6ef2de2..53ce1af 100644 --- a/libraries/libasyncio/FileOrDirectoryCompressTask.php +++ b/libraries/libasyncio/FileOrDirectoryCompressTask.php @@ -23,10 +23,12 @@ namespace libasyncio; +use GlobalLogger; use InvalidArgumentException; use libasyncio\compression\CompressionFormat; use libasyncio\compression\Compressor; use pocketmine\Server; +use RuntimeException; class FileOrDirectoryCompressTask extends FileOperationTask { @@ -68,7 +70,13 @@ public function __construct(string $input, string $output, callable $callable, ? public function onRun(): void { parent::onRun(); - $this->setSuccess(RecursiveCompressor::compress($this->input, $this->output, $this->compressionLevel, $this->compressor->getFormat())); + try { + $this->setSuccess(RecursiveCompressor::compress($this->input, $this->output, $this->compressionLevel, $this->compressor->getFormat())); + } catch (RuntimeException $e) { + GlobalLogger::get()->critical("Compression failed for {$this->input}: " . $e->getMessage()); + GlobalLogger::get()->logException($e); + $this->setSuccess(false); + } } protected function checkSuccess(): void diff --git a/libraries/libasyncio/FileOrDirectoryUncompressTask.php b/libraries/libasyncio/FileOrDirectoryUncompressTask.php index 694ec71..ecdea50 100644 --- a/libraries/libasyncio/FileOrDirectoryUncompressTask.php +++ b/libraries/libasyncio/FileOrDirectoryUncompressTask.php @@ -23,10 +23,12 @@ namespace libasyncio; +use GlobalLogger; use InvalidArgumentException; use libasyncio\compression\CompressionFormat; use libasyncio\compression\Compressor; use pocketmine\Server; +use RuntimeException; use function str_ends_with; class FileOrDirectoryUncompressTask extends FileOperationTask @@ -65,7 +67,13 @@ public function __construct(string $input, string $output, callable $callable, ? public function onRun(): void { parent::onRun(); - $this->setSuccess(RecursiveCompressor::uncompress($this->input, $this->output, $this->compressor->getFormat())); + try { + $this->setSuccess(RecursiveCompressor::uncompress($this->input, $this->output, $this->compressor->getFormat())); + } catch (RuntimeException $e) { + GlobalLogger::get()->critical("Uncompression failed for {$this->input}: " . $e->getMessage()); + GlobalLogger::get()->logException($e); + $this->setSuccess(false); + } } protected function checkSuccess(): void