Skip to content

Commit fcdc71d

Browse files
committed
feat: Support .gz archives
1 parent b1d281c commit fcdc71d

3 files changed

Lines changed: 128 additions & 13 deletions

File tree

src/Module/Archive/ArchiveFactory.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Internal\DLoad\Module\Archive;
66

77
use Closure as ArchiveMatcher;
8+
use Internal\DLoad\Module\Archive\Internal\GzArchive;
89
use Internal\DLoad\Module\Archive\Internal\NullArchive;
910
use Internal\DLoad\Module\Archive\Internal\PharArchive;
1011
use Internal\DLoad\Module\Archive\Internal\TarPharArchive;
@@ -116,6 +117,11 @@ private function bootDefaultMatchers(): void
116117
static fn(\SplFileInfo $info): Archive => new ZipPharArchive($info),
117118
), ['zip']);
118119

120+
$this->extend($this->matcher(
121+
'gz',
122+
static fn(\SplFileInfo $info): Archive => new GzArchive($info),
123+
), ['gz']);
124+
119125
$this->extend($this->matcher(
120126
'tar.gz',
121127
static fn(\SplFileInfo $info): Archive => new TarPharArchive($info),
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Internal\DLoad\Module\Archive\Internal;
6+
7+
use Internal\DLoad\Module\Archive\Exception\ArchiveException;
8+
9+
/**
10+
* Archive handler for standalone GZ (gzip) compressed files
11+
*
12+
* Uses PHP's zlib extension to decompress single gzip-compressed files.
13+
* The extracted file name is derived by stripping the `.gz` extension.
14+
*
15+
* @internal
16+
*/
17+
final class GzArchive extends Archive
18+
{
19+
public function extract(): \Generator
20+
{
21+
$sourcePath = $this->asset->getRealPath() ?: $this->asset->getPathname();
22+
23+
$gz = \gzopen($sourcePath, 'rb');
24+
$gz !== false or throw new ArchiveException(
25+
\sprintf('Could not open "%s" for reading.', $this->asset->getPathname()),
26+
);
27+
28+
try {
29+
// Derive output filename by stripping .gz extension
30+
$outputName = \preg_replace('/\.gz$/i', '', $this->asset->getFilename());
31+
$tempPath = \sys_get_temp_dir() . \DIRECTORY_SEPARATOR . $outputName;
32+
33+
$out = \fopen($tempPath, 'wb');
34+
$out !== false or throw new ArchiveException(
35+
\sprintf('Could not create temporary file "%s".', $tempPath),
36+
);
37+
38+
try {
39+
while (!\gzeof($gz)) {
40+
$chunk = \gzread($gz, 8192);
41+
if ($chunk === false) {
42+
break;
43+
}
44+
\fwrite($out, $chunk);
45+
}
46+
} finally {
47+
\fclose($out);
48+
}
49+
50+
$fileInfo = new \SplFileInfo($tempPath);
51+
52+
/** @var \SplFileInfo|null $fileTo */
53+
$fileTo = yield $fileInfo->getPathname() => $fileInfo;
54+
55+
if ($fileTo instanceof \SplFileInfo) {
56+
\copy($tempPath, $fileTo->getRealPath() ?: $fileTo->getPathname());
57+
}
58+
} finally {
59+
\gzclose($gz);
60+
}
61+
}
62+
}

tests/Acceptance/DLoadTest.php

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,31 @@ public function testDownloadsTrapPharSuccessfullyWithForceOption(): void
8888
}
8989
}
9090

91+
public function testDownloadsTomlTestGzBinary(): void
92+
{
93+
// Arrange
94+
$dload = $this->buildDLoad($this->createTomlTestXmlConfig());
95+
$downloadConfig = new DownloadConfig();
96+
$downloadConfig->software = 'toml-test';
97+
$downloadConfig->version = '2.1.0';
98+
$downloadConfig->type = Type::Binary;
99+
$downloadConfig->extractPath = (string) $this->destinationDir;
100+
101+
// Act
102+
$dload->addTask($downloadConfig);
103+
$dload->run();
104+
105+
// Assert - Check that toml-test binary was downloaded and extracted from .gz
106+
$os = OperatingSystem::fromGlobals();
107+
$expectedPath = (string) $this->destinationDir->join('toml-test' . $os->getBinaryExtension());
108+
self::assertFileExists($expectedPath, 'toml-test binary should be downloaded and extracted from .gz archive');
109+
self::assertGreaterThan(1024, \filesize($expectedPath), 'Downloaded binary should have substantial size');
110+
111+
if (\PHP_OS_FAMILY !== 'Windows') {
112+
self::assertTrue(\is_executable($expectedPath), 'Binary file should be executable');
113+
}
114+
}
115+
91116
public function testDownloadsTrapBinary(): void
92117
{
93118
// Arrange
@@ -120,29 +145,51 @@ protected function setUp(): void
120145
$this->tempDir = $this->testRuntimeDir->join('temp');
121146
$this->destinationDir = $this->testRuntimeDir;
122147

123-
// Initialize DLoad through Bootstrap
148+
$this->dload = $this->buildDLoad($this->createTrapXmlConfig());
149+
}
150+
151+
protected function tearDown(): void
152+
{
153+
// Clean up test directories
154+
if ($this->testRuntimeDir->isDir()) {
155+
$this->removeDirectory($this->testRuntimeDir);
156+
}
157+
}
158+
159+
/**
160+
* @return non-empty-string
161+
*/
162+
private function buildDLoad(string $xmlConfig): DLoad
163+
{
124164
$container = Bootstrap::init()
125-
->withConfig(
126-
$this->createTrapXmlConfig(),
127-
[],
128-
[],
129-
\getenv(),
130-
)
165+
->withConfig($xmlConfig, [], [], \getenv())
131166
->finish();
132167
$container->set($input = new ArgvInput(), InputInterface::class);
133168
$container->set($output = new BufferedOutput(), OutputInterface::class);
134169
$container->set(new SymfonyStyle($input, $output), StyleInterface::class);
135170
$container->set(new Logger($output));
136171

137-
$this->dload = $container->get(DLoad::class);
172+
return $container->get(DLoad::class);
138173
}
139174

140-
protected function tearDown(): void
175+
/**
176+
* @return non-empty-string
177+
*/
178+
private function createTomlTestXmlConfig(): string
141179
{
142-
// Clean up test directories
143-
if ($this->testRuntimeDir->isDir()) {
144-
$this->removeDirectory($this->testRuntimeDir);
145-
}
180+
return <<<XML
181+
<?xml version="1.0"?>
182+
<dload temp-dir="{$this->tempDir}" >
183+
<registry overwrite="false">
184+
<software name="TOML Test" alias="toml-test">
185+
<repository type="github" uri="toml-lang/toml-test"
186+
asset-pattern="/^toml-test-.*/"
187+
/>
188+
<binary name="toml-test" pattern="/^toml-test-.*/" version-command="version" />
189+
</software>
190+
</registry>
191+
</dload>
192+
XML;
146193
}
147194

148195
/**

0 commit comments

Comments
 (0)