Skip to content

Instantly share code, notes, and snippets.

@jszobody
Created December 7, 2017 20:40
Show Gist options
  • Save jszobody/e10feecb4b8b7ceae2588850ee75ff47 to your computer and use it in GitHub Desktop.
Save jszobody/e10feecb4b8b7ceae2588850ee75ff47 to your computer and use it in GitHub Desktop.
<?php
namespace App\Services;
use App\Models\ZipFile;
use Illuminate\Support\Collection;
use ZipStream\Exception\FileNotFoundException;
use ZipStream\ZipStream as ZipStreamBase;
class ZipStream extends ZipStreamBase
{
/**
* @var array
*/
protected $fileList = [];
/**
* @var int
*/
protected $fileSizes = 0;
/**
* @var int
*/
protected $filepathLengths = 0;
/**
* @var int
*/
protected $bytesSent = 0;
/**
* @var
*/
protected $cacheStream;
/**
* @param null $name
* @param array $opt
*/
public function __construct($name = null, $opt = [])
{
// We want all files treated like large files, which uses 'store' and no compression
$opt['large_file_size'] = 1;
parent::__construct($name, $opt);
}
/**
* @param $name
*
* @return $this
*/
public function setName($name)
{
$this->output_name = $name;
$this->need_headers = $name || $this->opt[self::OPTION_SEND_HTTP_HEADERS];
return $this;
}
/**
* Hand me a writable output stream to this method, and we'll save the zip data as we stream it out.
*
* @param $stream
*/
public function cacheZip($stream)
{
$this->cacheStream = $stream;
}
/**
* @param String $str
*/
protected function send($str) {
$this->bytesSent += strlen($str);
if(is_resource($this->cacheStream)) {
fwrite($this->cacheStream, $str);
}
parent::send($str);
}
/**
* @param Collection $fileList
*
* @return $this
*/
public function queue(Collection $fileList)
{
$fileList->each(function ($file) {
$this->queueFile($file);
});
return $this;
}
/**
* @param ZipFile $file
*/
public function queueFile(ZipFile $file)
{
if(array_key_exists($file->getAbsolutePath(), $this->fileList)) {
return;
}
$this->fileList[$file->getAbsolutePath()] = $file;
$this->fileSizes += max(2, $file->getFilesize());
$this->filepathLengths += strlen($file->getZipPath());
}
/**
* @param string $cachePath
*
* @return $this
*/
public function process($cachePath = null)
{
if(is_null($cachePath) || !is_writable(dirname($cachePath))) {
$this->buildAndSend();
return $this;
}
$this->cacheStream = fopen($cachePath, 'w');
$this->buildAndSend();
fclose($this->cacheStream);
return $this;
}
/**
*
*/
protected function buildAndSend()
{
$this->sendContentLengthHeader();
foreach($this->fileList AS $file) {
$this->processFile($file);
}
$this->finish();
}
/**
* @param $file
*
* @throws FileNotFoundException
* @throws \ZipStream\Exception\FileNotReadableException
*/
protected function processFile(ZipFile $file)
{
if(starts_with($file->getAbsolutePath(), "s3://")) {
$this->addFileFromStream($file->getZipPath(), $file->getHandle());
fclose($file->getHandle());
} else {
$this->addFileFromPath($file->getZipPath(), $file->getAbsolutePath());
}
}
/**
* @return int
*/
public function getBytesSent()
{
return $this->bytesSent;
}
/**
* We can do this!
*/
protected function sendContentLengthHeader()
{
header("Content-Length: " . $this->calculateZipSize());
}
/**
* See http://stackoverflow.com/a/19380600/660694
*
* @return int
*/
public function calculateZipSize()
{
return count($this->fileList) * (30 + 46) + (2 * $this->filepathLengths) + $this->fileSizes + 22;
}
/**
* @param callable $errorCallback
*
* @return $this
*/
public function sanityCheckFileSize(callable $errorCallback)
{
if($this->calculateZipSize() != $this->getBytesSent()) {
$errorCallback($this->calculateZipSize(), $this->getBytesSent());
}
return $this;
}
}
@freekmurze
Copy link

Very insightful! Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment