Skip to content

Commit

Permalink
Calculate ZIP Size (#241)
Browse files Browse the repository at this point in the history
Calculates the resulting file size with the minimal amount of work
that is required.

Fixes #89
  • Loading branch information
maennchen authored Apr 27, 2023
1 parent ca4e123 commit c9a2e4f
Show file tree
Hide file tree
Showing 8 changed files with 863 additions and 160 deletions.
110 changes: 36 additions & 74 deletions guides/ContentLength.rst
Original file line number Diff line number Diff line change
@@ -1,85 +1,47 @@
Adding Content-Length header
=============

Adding a ``Content-Length`` header for ``ZipStream`` is not trivial since the
size is not known beforehand.
Adding a ``Content-Length`` header for ``ZipStream`` can be achieved by
using the options ``SIMULATION_STRICT`` or ``SIMULATION_LAX`` in the
``operationMode`` parameter.

The following workaround adds an approximated header:
In the ``SIMULATION_STRICT`` mode, ``ZipStream`` will not allow to calculate the
size based on reading the whole file. ``SIMULATION_LAX`` will read the whole
file if neccessary.

.. code-block:: php
``SIMULATION_STRICT`` is therefore useful to make sure that the size can be
calculated efficiently.

use ZipStream\CompressionMethod;
.. code-block:: php
use ZipStream\OperationMode;
use ZipStream\ZipStream;
class Zip
{
private $files = [];
public function __construct(
private readonly string $name
) { }
public function addFile(
string $name,
string $data,
): void {
$this->files[] = ['type' => 'addFile', 'name' => $name, 'data' => $data];
}
public function addFileFromPath(
string $name,
string $path,
): void {
$this->files[] = ['type' => 'addFileFromPath', 'name' => $name, 'path' => $path];
$zip = new ZipStream(
operationMode: OperationMode::SIMULATE_STRICT, // or SIMULATE_LAX
defaultEnableZeroHeader: false,
sendHttpHeaders: true,
outputStream: $stream,
);
// Normally add files
$zip->addFile('sample.txt', 'Sample String Data');
// Use addFileFromCallback and exactSize if you want to defer opening of
// the file resource
$zip->addFileFromCallback(
'sample.txt',
exactSize: 18,
callback: function () {
return fopen('...');
}
);
public function getEstimate(): int {
$estimate = 22;
foreach ($this->files as $file) {
$estimate += 76 + 2 * strlen($file['name']);
if ($file['type'] === 'addFile') {
$estimate += strlen($file['data']);
}
if ($file['type'] === 'addFileFromPath') {
$estimate += filesize($file['path']);
}
}
return $estimate;
}
public function finish()
{
header('Content-Length: ' . $this->getEstimate());
$zip = new ZipStream(
outputName: $this->name,
SendHttpHeaders: true,
enableZip64: false,
defaultCompressionMethod: CompressionMethod::STORE,
);
foreach ($this->files as $file) {
if ($file['type'] === 'addFile') {
$zip->addFile(
fileName: $file['name'],
data: $file['data'],
);
}
if ($file['type'] === 'addFileFromPath') {
$zip->addFileFromPath(
fileName: $file['name'],
path: $file['path'],
);
}
}
$zip->finish();
}
}
It only works with the following constraints:

- All file content is known beforehand.
- Content Deflation is disabled
// Read resulting file size
$size = $zip->finish();
// Tell it to the browser
header('Content-Length: '. $size);
// Execute the Simulation and stream the actual zip to the client
$zip->executeSimulation();
Thanks to
`partiellkorrekt <https://github.com/maennchen/ZipStream-PHP/issues/89#issuecomment-1047949274>`_
for this workaround.
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
<!-- Turn off dead code warnings for externally called functions -->
<PossiblyUnusedProperty errorLevel="suppress" />
<PossiblyUnusedMethod errorLevel="suppress" />
<PossiblyUnusedReturnValue errorLevel="suppress" />
</issueHandlers>
</psalm>
23 changes: 23 additions & 0 deletions src/Exception/FileSizeIncorrectException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace ZipStream\Exception;

use ZipStream\Exception;

/**
* This Exception gets invoked if a file is not as large as it was specified.
*/
class FileSizeIncorrectException extends Exception
{
/**
* @internal
*/
public function __construct(
public readonly int $expectedSize,
public readonly int $actualSize
) {
parent::__construct("File is {$actualSize} instead of {$expectedSize} bytes large. Adjust `exactSize` parameter.");
}
}
19 changes: 19 additions & 0 deletions src/Exception/SimulationFileUnknownException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace ZipStream\Exception;

use ZipStream\Exception;

/**
* This Exception gets invoked if a strict simulation is executed and the file
* information can't be determined without reading the entire file.
*/
class SimulationFileUnknownException extends Exception
{
public function __construct()
{
parent::__construct('The details of the strict simulation file could not be determined without reading the entire file.');
}
}
Loading

0 comments on commit c9a2e4f

Please sign in to comment.