Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions libraries/libasyncio/FileOrDirectoryCompressTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,43 @@

namespace libasyncio;

use GlobalLogger;
use InvalidArgumentException;
use libasyncio\compression\CompressionFormat;
use libasyncio\compression\Compressor;
use pocketmine\Server;
use RuntimeException;

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.
*
* @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)
{
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 = 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);
}
Expand All @@ -57,17 +70,23 @@ 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));
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
{
$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 {
Server::getInstance()->getLogger()->error("Unable to compress file {$this->input} to {$outputLocation}");
}
}

}
}
36 changes: 29 additions & 7 deletions libraries/libasyncio/FileOrDirectoryUncompressTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,40 @@

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
{

/** @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);
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);
}
Expand All @@ -53,17 +67,25 @@ public function __construct(string $input, string $output, callable $callable)
public function onRun(): void
{
parent::onRun();
$this->setSuccess(ZstdRecursiveCompressor::uncompress($this->input, $this->output));
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
{
$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 {
Server::getInstance()->getLogger()->error("Unable to uncompress file {$inputLocation} to {$this->output}");
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,46 +24,41 @@
namespace libasyncio;

use GlobalLogger;
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.
* The output should be a directory
* 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);
Expand All @@ -73,12 +68,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);
Expand All @@ -91,20 +83,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'
);
}

Expand All @@ -113,17 +112,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.");
Expand All @@ -138,4 +134,19 @@ public static function uncompress(string $input, string $output): bool

return true;
}
}

/**
* @param CompressionFormat|null $format
* @param string|null $path
*
* @return Compressor
*/
private static function resolveCompressor(?CompressionFormat $format, ?string $path = null): Compressor
{
if ($format !== null) {
return $format->getCompressor();
}

return ($path !== null ? (CompressionFormat::fromPath($path) ?? CompressionFormat::auto()) : CompressionFormat::auto())->getCompressor();
}
}
6 changes: 5 additions & 1 deletion libraries/libasyncio/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading