Coder Social home page Coder Social logo

laravel-zipstream's Introduction

Streaming Zips with Laravel

Latest Version on Packagist Total Downloads Software License Build Status

A fast and simple streaming zip file downloader for Laravel.

  • Builds zip files from local or S3 file sources, or any other PSR7 stream.
  • Provides a direct download stream to your user. The zip download begins immediately even though the zip is still being created. No need to save the zip to disk first.
  • Calculates the zip filesize up front for the Content-Length header. The user gets an accurate download time estimate in their browser.
  • Built on top of the excellent ZipStream-PHP library.

Quickstart

1. Install the package

composer require stechstudio/laravel-zipstream

The service provider and facade will be automatically wired up.

2. In a controller method call the create method on the Zip facade

use Zip;

class ZipController {

    public function build()
    {
        return Zip::create("package.zip", [
            "/path/to/Some File.pdf",
            "/path/to/Export.xlsx"       
        ]);
    }
}

That's it! A StreamedResponse will be returned and the zip contents built and streamed out. The user's browser will begin downloading a package.zip file immediately.

Customize the internal zip path for a file

By default any files you add will be stored in the root of the zip, with their original filenames.

You can customize the filename and even create subfolders within the zip by providing your files array with key/value pairs:

Zip::create("package.zip", [

    // Will be stored as `Some File.pdf` in the zip
    "/path/to/Some File.pdf",          
 
    // Will be stored as `Export.xlsx` in the zip
    "/path/to/data.xlsx" => 'Export.xlsx',
 
    // Will create a `log` subfolder in the zip and be stored as `log/details.txt`
    "/path/to/log.txt" => "log/details.txt"
 
]);

Fluent usage

You can also provide your files one at a time:

Zip::create("package.zip")
    ->add("/path/to/Some File.pdf")
    ->add("/path/to/data.xlsx", 'Export.xlsx')
    ->add("/path/to/log.txt", "log/details.txt");

Add HTTP file sources

You can add HTTP URLs as the source filepath. Note that zip filesize can only be calculated up front if the HTTP source provides a Content-Length header, not all URLs do.

Zip::create("package.zip")
    ->add("https://...", "myfile.pdf");

Add raw file data

You can provide raw data instead of a filepath:

Zip::create("package.zip")
    ->addRaw("...file contents...", "hello.txt");

Support for S3

Install AWS sdk and configure S3

You can stream files from S3 into your zip.

  1. Install the aws/aws-sdk-php package

  2. Setup an AWS IAM user with s3:GetObject permission for the S3 bucket and objects you intend to zip up.

  3. Store your credentials as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION in your .env file.

Add S3 files to your zip

Provide s3:// paths when creating the zip:

Zip::create("package.zip")
    ->add("s3://bucket-name/path/to/object.pdf", "Something.pdf");

By default, this package will try to create an S3 client using the same .env file credentials that Laravel uses. If needed, you can wire up a custom S3 client to the zipstream.s3client container key. Or you can even pass in your own S3 client when adding a file to the zip. To do this, you'll need to create an S3File model instance yourself so that you can provide the client, like this:

use STS\ZipStream\Models\S3File;

// Create your own client however necessary
$s3 = new Aws\S3\S3Client();

Zip::create("package.zip")->add(
    S3File::make("s3://bucket-name/path/to/object.pdf")->setS3Client($s3)
);

Zip size prediction

By default this package attempts to predict the final zip size and sends a Content-Length header up front. This means users will see accurate progress on their download, even though the zip is being streamed out as it is created!

This only works if files are not compressed.

If you have issues with the zip size prediction you can disable it with ZIPSTREAM_PREDICT_SIZE=false in your .env file.

Specify your own filesizes

It can be expensive retrieving filesizes for some file sources such as S3 or HTTP. These require dedicated calls, and can add up to a lot of time if you are zipping up many files. If you store filesizes in your database and have them available, you can drastically improve performance by providing filesizes when you add files. You'll need to make your own File models instead of adding paths directly to the zip.

Let's say you have a collection of Eloquent $files, are looping through and building a zip. If you have a filesize attribute available, it would look something like this:

use STS\ZipStream\Models\File;

// Retrieve file records from the database
$files = ...;

$zip = Zip::create("package.zip");

foreach($files AS $file) {
    $zip->add(
        File::make($file->path, $file->name)->setFilesize($file->size)
    );
}

Configure compression

By default this package uses no compression. Why?

  1. This makes building the zips super fast, and is light on your CPU
  2. This makes it possible to predict the final zip size as mentioned above.

If you want to compress your zip files set ZIPSTREAM_FILE_METHOD=deflate in your .env file. Just realize this will disable the Content-Length header.

Save Zip to disk

Even though the primary goal of this package is to enable zip downloads without saving to disk, there may be times you'd like to generate a zip on disk as well. And you might as well make use of this package to do so.

Use the saveTo method to write the entire zip to disk immediately. Note that this expects a folder path, the zip name will be appended.

Zip::create("package.zip")
    // ... add files ...
    ->saveTo("/path/to/folder");

And yes, if you've properly setup and configured S3 you can even save to an S3 bucket/path.

Zip::create("package.zip")
    // ... add files ...
    ->saveTo("s3://bucket-name/path/to/folder");

Caching zip while still streaming download

What if you have a lot of users requesting the same zip payload? It might be nice to stream out the zip while also caching it to disk for the future.

Use the cache method to provide a cache path. Note this should be the entire path including filename.

Zip::create("package.zip")
    // ... add files ...
    ->cache("/path/to/folder/some-unique-cache-name.zip");

You might use an internal DB id for your cache name, so that the next time a user requests a zip download you can determine if one is already built and just hand it back.

Events

  • STS\ZipStream\Events\ZipStreaming: Dispatched when a new zip stream begins processing
  • STS\ZipStream\Events\ZipStreamed: Dispatched when a zip finishes streaming
  • STS\ZipStream\Events\ZipSizePredictionFailed: Fired if the predicted filesize doesn't match the final size. If you have filesize prediction enabled it's a good idea to listen for this event and log it, since that might mean the zip download failed or was corrupt for your user.

Filename sanitization

By default this package will try to translate any non-ascii character in filename or folder's name to ascii. For example, if your filename is 中文_にほんご_Ч_Ɯ_☺_someascii.txt. It will become __C___someascii.txt using Laravel's Str::ascii($path).

If you need to preserve non-ascii characters, you can disable this feature with an .env setting:

ZIPSTREAM_FILE_SANITIZE=false

License

The MIT License (MIT). Please see License File for more information.

laravel-zipstream's People

Contributors

andreacivita avatar dp88 avatar fillmoreb avatar firtzberg avatar gitskaydomrose avatar haz5 avatar it-can avatar jszobody avatar laravel-shift avatar miraries avatar ryangjchandler avatar taiyou-tw avatar toto4ds avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-zipstream's Issues

Usage help

Hi there, I can get this package writing to my local storage directory. This is great.

However, I'm trying to use it to write to Azure Blob Storage (using the package matthewbdaly/laravel-azure-storage).

Using the package mentioned, I have my Azure blob storage configured as a 'storage disk' in Laravel which I can write to using commands like Storage::disk('azure')->put('example.txt', 'Contents');

Do you know how I'd get this package to write the zip file output to this disk?

Kind regards
Jason

ZipStream::create() should not be called statically

Hi,

I am getting "Non-static method STS\ZipStream\ZipStream::create() should not be called statically" error message when i import the package via use STS\ZipStream\ZipStream;
when i import via use ZipStream I get "Class 'ZipStream' not found".

Cached config

Thanks, it's very handy package.

I have just noticed, there is an issue with s3 when you cache the config
php artisan config:cache

Exception

GuzzleHttp \ Exception \ ConnectException

Tested on clean Laravel 5.8 installation.

What worked for me:
config.php

...
'aws' => [
    'region' => env('ZIPSTREAM_AWS_REGION', env('AWS_DEFAULT_REGION', 'us-east-1')),
    'key' => env('AWS_ACCESS_KEY_ID', ''),
    'secret' => env('AWS_ACCESS_KEY_KEY', '')
 ]
...

S3File.php

...
/**
 * @return S3Client
 */
public function getS3Client(): S3Client
{
    if (!$this->client) {
        $this->client = new Aws\S3\S3Client([
            'credentials' => [
                'key'    => config('zipstream.aws.key'),
                'secret' => config('zipstream.aws.secret')
            ],
            'region' => $this->getRegion(),
            'version' => '2006-03-01'
        ]);
    }

    return $this->client;
}
...

Hope this helps.

"STS\ZipStream\Exceptions\FilenameMissingException" Unable to add file but file exists

$zip_file = Zip::create("myfile.zip");
$zip_file->add(storage_path('app/files/qPygS13dEaa8KTDnzvx69fVBwqXiSJl6YX06NvcJ.pdf'));
return $zip_file;

Hi. I'm using the above code to try and ZIP a PDF file on my local Laravel machine but I get the exception in the title. The file definitely exists. This happens for all files I try to add.
When I use return response()->download(storage_path('app/files/qPygS13dEaa8KTDnzvx69fVBwqXiSJl6YX06NvcJ.pdf')); as a test it returns the file correctly.

Is there something that I'm doing wrong? My dev enviroment is a Win10 machine with XAMPP running for dev purposes.
Any help would be great!
Thanks.

Use defaults instead of constantly trying to find the config values

Description

The code relies on the fact that a configuration file (config/zipstream.php) is published to work. As it defines defaults, it should not rely on the configuration values if the configuration file has not been published.

Expected results

  • The method src/ZipStreamServiceProvider::register() specify defaults values in case the call to get values from the configuration key is not found.
  • The documentation precise in the Quick start that the command to publish config/zipstream.php is not mandatory, but only useful if the end developper need to change the defaults.

Notes

Here is a stack trace for the error I got when I did not had the config file published:

[2020-11-13 11:36:25] prod.ERROR: Argument 1 passed to STS\ZipStream\ZipStreamServiceProvider::buildArchiveOptions() must be of the type array, null given, called in /home/ubuntu/my-project/vendor/stechstudio/laravel-zipstream/src/ZipStreamServiceProvider.php on line 40 {"userId":8028,"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Argument 1 passed to STS\\ZipStream\\ZipStreamServiceProvider::buildArchiveOptions() must be of the type array, null given, called in /home/ubuntu/my-project/vendor/stechstudio/laravel-zipstream/src/ZipStreamServiceProvider.php on line 40 at /home/ubuntu/my-project/vendor/stechstudio/laravel-zipstream/src/ZipStreamServiceProvider.php:55)
[stacktrace]
#0 /home/ubuntu/my-project/vendor/stechstudio/laravel-zipstream/src/ZipStreamServiceProvider.php(40): STS\\ZipStream\\ZipStreamServiceProvider->buildArchiveOptions()
#1 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(799): STS\\ZipStream\\ZipStreamServiceProvider->STS\\ZipStream\\{closure}()
#2 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(681): Illuminate\\Container\\Container->build()
#3 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(785): Illuminate\\Container\\Container->resolve()
#4 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(629): Illuminate\\Foundation\\Application->resolve()
#5 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(770): Illuminate\\Container\\Container->make()
#6 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(945): Illuminate\\Foundation\\Application->make()
#7 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(873): Illuminate\\Container\\Container->resolveClass()
#8 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(834): Illuminate\\Container\\Container->resolveDependencies()
#9 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(681): Illuminate\\Container\\Container->build()
#10 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(785): Illuminate\\Container\\Container->resolve()
#11 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(265): Illuminate\\Foundation\\Application->resolve()
#12 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(799): Illuminate\\Container\\Container->Illuminate\\Container\\{closure}()
#13 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(681): Illuminate\\Container\\Container->build()
#14 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(785): Illuminate\\Container\\Container->resolve()
#15 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(629): Illuminate\\Foundation\\Application->resolve()
#16 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(770): Illuminate\\Container\\Container->make()
#17 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Container/Container.php(1245): Illuminate\\Foundation\\Application->make()
#18 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(198): Illuminate\\Container\\Container->offsetGet()
#19 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(166): Illuminate\\Support\\Facades\\Facade::resolveFacadeInstance()
#20 /home/ubuntu/my-project/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(255): Illuminate\\Support\\Facades\\Facade::getFacadeRoot()

Which is kind of weird, because in the register method in the service provider, you correctly merge the config file of your package with mine. I wonder if the issue comes because it tries to merge null (I don't have any config file published) with yours?

What about private files?

Hello,

I have some files stored in private/ instead of public/. This is how I store them:

Storage::put($fileName, $content, 'private');

When I run:

$files = collect(Storage::files("/private/{$this->user->id}/backups/backup_{$this->user->backup->id}/"))
            ->map(fn($file) => Storage::path($file)) // get the absolute path
            ->toArray();

return Zip::create("backup.zip", $files);

I get:

fopen(/var/www/html/storage/app/private/1/backups/backup_955ef575-ba3f-42ee-a1da-4efaa87edca2/10414.pdf): Failed to open stream: Permission denied

Screenshot 2022-01-16 185443

I am using laravel 8 with sail in an ubuntu wsl2 container. The storage facade can read and write to the private folder without an issue.

When I run the same code above but with files from the public folder it works perfectly.

filesize(): stat failed error

Hi everyone!
Thanks for this package! It works great.
However, I'm running into an issue while trying to test the package working within my application.

In order to test the zipping, I fake a file & test that it exists, which gives no error, but when i call Zip::create() i get the following error.

Greetings,
Facundo.

it('can download all files', function () {
    UploadedFile::fake()->image('foo.jpg')->size(400)
        ->storeAs("{$this->job->folder_path}9.Issued Docs/", 'foo.jpg', [
            'disk' => 'jobs',
        ]);

    Storage::disk('jobs')->assertExists("{$this->job->folder_path}9.Issued Docs/foo.jpg");

    livewire(ListDownloadbleFiles::class, [
        'job' => $this->job,
        'user' => $this->user,
    ])
        ->call('downloadAll')
        ->assertDownload();
});
  at vendor/stechstudio/laravel-zipstream/src/Models/LocalFile.php:15
     11▕      * @return int
     12▕      */
     13public function calculateFilesize(): int
     14▕     {
  ➜  15return filesize($this->getSource());
     16▕     }
     1718▕     /**
     19▕      * @return StreamInterface

Add file from data

Hi, I'm trying to add a new file to the archive from raw data (string).
I tried using the addFile() method from the underlying ZipStream implementation, but when I do so nothing gets downloaded. The content is dumped to the browser window instead.
Here's a sample code.

use STS\ZipStream\ZipStreamFacade;

$zipStream = ZipStreamFacade::create('name.zip');
$zipStream->addFile('main.json', 'raw content');
return $zipStream;

Wrong filename when used in Livewire

Asking this question feels a bit stupid, but maybe you can still help me with my problem.

I'm using code like the following to return a response from a Livewire action.

$zip = Zip::create("tasks-{$ts}.zip");
foreach ($files as $files) {
    $zip->add($file->path, $file->name);
}
return $zip->response();

Everything works as expected except that the downloaded file has the wrong name. It is always called download.txt. So the name I pass to the Zip::create is completely ignored.
I won't exclude that it comes from some code of my app, but I couldn't find anything yet.

failed to open stream: No such file or directory

When trying to save a zip to disk I get a failed to open stream: No such file or directory error.

To reproduce:

$zipFIle = Zip::create('global.zip');

** add files **

$zipFile->save(storage_path('exports');

Laravel version: 8.27
PHP: 7.4

Save To Error

Hi Sir, Good Day.
I got error when i want to save to in my storage folder.

STS\ZipStream\Exceptions\FilenameMissingException

Could you give me solution ?

Thank You Sir

Not compressing

Thanks for this package.
Zip file is created and streams nicely.
Just that it never compresses the Zip.

I have looked at your readme, and that of spatie Zipstream and looks like I'm doing all that is recommended...

Code:

        $options = new ZipStream\Option\Archive();
        $options->setDeflateLevel(6);
        $options->setLargeFileMethod(\ZipStream\Option\Method::DEFLATE());
        $options->setLargeFileSize(1);

        $fileOptions = new ZipStream\Option\File();
        $fileOptions->setDeflateLevel(6);
        $fileOptions->setMethod(\ZipStream\Option\Method::DEFLATE());

        $zip = new STS\ZipStream\ZipStream($options, $fileOptions);

        return $zip->create('package.zip', [
            storage_path('app/public/sample.csv')
        ]);

I also have this set in .env file:
ZIPSTREAM_FILE_METHOD=deflate

Any ideas on how to adjust so that the zip is compressed?

PHP8 support

My question is, will this package also support PHP 8 in the future?

v1.6 FilenameMissingException error?

Since 1.6 I get this error

STS\ZipStream\Exceptions\FilenameMissingException

my code:

$files = [
'/tmp/test.pdf',
'/tmp/test2.pdf',
];

// Zip it
        Zip::create('file.zip', $files)
            ->saveTo(storage_path('app/tmp'));

on 1.5 it works

How To Dynamically Build ZIP

How would you go about adding files dynamically to the zip? I have an array of file paths in my view. I want to give the user the option to download a zip instead of each file separately. I'm thinking about an anchor tag that links to the zip. But somehow I have to create that zip with the assets present in the view.

The only solution I can come up with is a GET request with the asset paths as parameters. The GET request then triggers the build of the zip and returns the stream. Does this make sense? Any other way I don't see?

Thanks a lot for you great work!

Error executing "PutObject" EntityTooLarge

EntityTooLargeYour proposed upload exceeds the maxim (truncated...)
EntityTooLarge (client): Your proposed upload exceeds the maximum allowed size -
EntityTooLargeYour proposed upload exceeds the maximum allowed size74370992865368709120

We have Zips larger than 5GB and is there a way to use uploader rather than putObject, we are calling saveTo function

Call to undefined function GuzzleHttp\Psr7\stream_for()

i cant download return error 5 with this message Call to undefined function GuzzleHttp\Psr7\stream_for()
[2021-07-06 15:13:06] local.ERROR: Call to undefined function GuzzleHttp\Psr7\stream_for() {"exception":"[object] (Error(code: 0): Call to undefined function GuzzleHttp\Psr7\stream_for() at D:\Work\medicaus\QR-Pdf\vendor\stechstudio\laravel-zipstream\src\ZipStream.php:274)
[stacktrace]
#0 D:\Work\medicaus\QR-Pdf\vendor\stechstudio\laravel-zipstream\src\ZipStream.php(308): STS\ZipStream\ZipStream->getOutputStream()
#1 D:\Work\medicaus\QR-Pdf\vendor\maennchen\zipstream-php\src\ZipStream.php(555): STS\ZipStream\ZipStream->send('PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00...')
#2 D:\Work\medicaus\QR-Pdf\vendor\maennchen\zipstream-php\src\ZipStream.php(375): ZipStream\ZipStream->addCdrEof()
#3 D:\Work\medicaus\QR-Pdf\vendor\stechstudio\laravel-zipstream\src\ZipStream.php(172): ZipStream\ZipStream->finish()
#4 D:\Work\medicaus\QR-Pdf\vendor\stechstudio\laravel-zipstream\src\ZipStream.php(204): STS\ZipStream\ZipStream->process()
#5 D:\Work\medicaus\QR-Pdf\vendor\symfony\http-foundation\StreamedResponse.php(109): STS\ZipStream\ZipStream->STS\ZipStream\{closure}()
#6 D:\Work\medicaus\QR-Pdf\vendor\symfony\http-foundation\Response.php(394): Symfony\Component\HttpFoundation\StreamedResponse->sendContent()
#7 D:\Work\medicaus\QR-Pdf\vendor\laravel\framework\src\Illuminate\Support\HigherOrderTapProxy.php(34): Symfony\Component\HttpFoundation\Response->send()
#8 D:\Work\medicaus\QR-Pdf\public\index.php(53): Illuminate\Support\HigherOrderTapProxy->__call('send', Array)
#9 D:\Work\medicaus\QR-Pdf\server.php(21): require_once('D:\\Work\\medicau...')
#10 {main}

Missing example for files in DB

Hi,
I am truing to figure out how to use your package in case the files are not stored on a disk.
Ideally it should look like following:

return Zip::create("package.zip", function($zip) {
  // load data files in portions
  DataModel::chunk(200, function ($dataModels) use($zip) {
    foreach ($dataModels as $dataModel) {
        // add file from DB field to zip
        $zip->add($dataModel->dataField, $dataModel->fileName);
    }
  });
  return true; // all ok
});

so that you can really zip files on the fly.
Is this possible?

Laravel 5.5

Great work! Can the package be used in Laravel 5.5?

Cleanup after completion

Hello,

I don't know if this is even possible/practical, but I'm using this library to stream a temporary folder and would like to delete that folder after completion and, as the return Zip::create() is the last command on the controller, I can't run anything else.

Would be possible to add a callback to the library so code can be executed after completion?

Something like this:

return Zip::create("your_download.zip", $files)
    ->onCompletion(function () {
        Storage::disk('temp')->deleteDirectory($directory);
    });

Thanks!

Issue installing

Do you know what's going on here?

$ composer require stechstudio/laravel-zipstream
Your requirements could not be resolved to an installable set of packages.
  Problem 1
    - Can only install one of: maennchen/zipstream-php[2.0.0, 1.2.0].
    - Can only install one of: maennchen/zipstream-php[2.0.0, 1.2.0].
    - Can only install one of: maennchen/zipstream-php[2.0.0, 1.2.0].
    - stechstudio/laravel-zipstream 2.4 requires maennchen/zipstream-php ^v2.0 -> satisfiable by maennchen/zipstream-php[2.0.0].
    - Installation request for stechstudio/laravel-zipstream ^2.4 -> satisfiable by stechstudio/laravel-zipstream[2.4].
    - Installation request for maennchen/zipstream-php (locked at 1.2.0) -> satisfiable by maennchen/zipstream-php[1.2.0].

Instructions for Livewire

When working with Livewire, you're not able to return the ZipStream instance like you would in a controller. I believe that is down to Livewire not respecting the Responsable contract.

I then tried to return $myZipInstance->response() directly, but that also didn't work. The only way I could get it to work was by replicating the logic inside of ZipStream::response() using response()->streamDownload() instead:

// Inside of a Livewire action.
return response()->streamDownload(function () use ($myZipInstance) {
    $myZipInstance->process();
}, $myZipInstance->getName());

I'm wondering whether this library should instead be using Laravel's own utilities for generating streamed downloaded instead of constructing the StreamedResponse instance manually.

Zip fails when S3 links contains special characters

The zip streaming process fails when any of the s3 links contain special characters like '[' even though the file exists on S3.
Error:
Error executing "GetObject on "https://.." resulted in a 404 Not Found response.

There is no such error if I use maennchen's ZipStream-PHP but I need to stream zip back to S3.

Allow anonymous S3 clients

It is a recommended security practice to use machine level IAM roles instead of putting credentials in your code. To do this you need to be able to create a client using credentials = false instead of providing a key and secret. It doesn't appear that is possible presently.

I am getting error in window pc same code working in ubuntu( only in s3bucket zip normall zip is working )

I have getting error from STS\ZipStream\Exceptions\FilenameMissingException: in file for only window in xampp but it working in ubuntu. when zip create form s3bucket folder

STS\ZipStream\Exceptions\FilenameMissingException: in file C:\xampp\htdocs\test\vendor\stechstudio\laravel-zipstream\src\Models\TempFile.php on line 20

#0 C:\xampp\htdocs\test\vendor\stechstudio\laravel-zipstream\src\Models\File.php(42): STS\ZipStream\Models\TempFile->getDefaultZipPath()
#1 C:\xampp\htdocs\test\vendor\stechstudio\laravel-zipstream\src\Models\File.php(66): STS\ZipStream\Models\File->__construct('C:\xampp\htdocs...', NULL)
#2 C:\xampp\htdocs\test\vendor\stechstudio\laravel-zipstream\src\ZipStream.php(289): STS\ZipStream\Models\File::make('C:\xampp\htdocs...')
#3 C:\xampp\htdocs\test\app\Http\Controllers\API\PatientController.php(162): STS\ZipStream\ZipStream->saveTo('C:\xampp\htdocs...')
#4 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Controller.php(54): App\Http\Controllers\API\PatientController->downloadData(Object(Illuminate\Http\Request))
#5 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\ControllerDispatcher.php(45): Illuminate\Routing\Controller->callAction('downloadData', Array)
#6 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Route.php(239): Illuminate\Routing\ControllerDispatcher->dispatch(Object(Illuminate\Routing\Route), Object(App\Http\Controllers\API\PatientController), 'downloadData')
#7 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Route.php(196): Illuminate\Routing\Route->runController()
#8 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Router.php(685): Illuminate\Routing\Route->run()
#9 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(128): Illuminate\Routing\Router->Illuminate\Routing{closure}(Object(Illuminate\Http\Request))
#10 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Middleware\SubstituteBindings.php(41): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#11 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Illuminate\Routing\Middleware\SubstituteBindings->handle(Object(Illuminate\Http\Request), Object(Closure))
#12 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Middleware\ThrottleRequests.php(59): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#13 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Illuminate\Routing\Middleware\ThrottleRequests->handle(Object(Illuminate\Http\Request), Object(Closure), 600, '1')
#14 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Auth\Middleware\Authenticate.php(44): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#15 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Illuminate\Auth\Middleware\Authenticate->handle(Object(Illuminate\Http\Request), Object(Closure), 'api')
#16 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(103): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#17 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Router.php(687): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#18 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Router.php(662): Illuminate\Routing\Router->runRouteWithinStack(Object(Illuminate\Routing\Route), Object(Illuminate\Http\Request))
#19 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Router.php(628): Illuminate\Routing\Router->runRoute(Object(Illuminate\Http\Request), Object(Illuminate\Routing\Route))
#20 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Routing\Router.php(617): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request))
#21 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php(165): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request))
#22 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(128): Illuminate\Foundation\Http\Kernel->Illuminate\Foundation\Http{closure}(Object(Illuminate\Http\Request))
#23 C:\xampp\htdocs\test\app\Http\Middleware\SetLanguage.php(40): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#24 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): App\Http\Middleware\SetLanguage->handle(Object(Illuminate\Http\Request), Object(Closure))
#25 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Foundation\Http\Middleware\TransformsRequest.php(21): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#26 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Illuminate\Foundation\Http\Middleware\TransformsRequest->handle(Object(Illuminate\Http\Request), Object(Closure))
#27 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Foundation\Http\Middleware\TransformsRequest.php(21): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#28 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Illuminate\Foundation\Http\Middleware\TransformsRequest->handle(Object(Illuminate\Http\Request), Object(Closure))
#29 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Foundation\Http\Middleware\ValidatePostSize.php(27): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#30 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Illuminate\Foundation\Http\Middleware\ValidatePostSize->handle(Object(Illuminate\Http\Request), Object(Closure))
#31 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode.php(63): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#32 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode->handle(Object(Illuminate\Http\Request), Object(Closure))
#33 C:\xampp\htdocs\test\vendor\fruitcake\laravel-cors\src\HandleCors.php(57): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#34 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Fruitcake\Cors\HandleCors->handle(Object(Illuminate\Http\Request), Object(Closure))
#35 C:\xampp\htdocs\test\vendor\fideloper\proxy\src\TrustProxies.php(57): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#36 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(167): Fideloper\Proxy\TrustProxies->handle(Object(Illuminate\Http\Request), Object(Closure))
#37 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php(103): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline{closure}(Object(Illuminate\Http\Request))
#38 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php(140): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#39 C:\xampp\htdocs\test\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php(109): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#40 C:\xampp\htdocs\test\public\index.php(55): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#41 {main}

Handle missing files

In the current implemention, if one of the requested files is missing (or the request throws an error), this breaks the entire zip file. Would it be possible to make this a bit more resilient, and just skip it?

error buildArchiveOptions() must be of the type array

Hi,

I was doing the same simple test via tinker on 2 different machines, and I get different results.

image

image

What is going wrong?

The version of Laravel is the same, the only difference I see is that in the test that does not fail, the version of php is 7.2.10, and the one that fails is 7.2.24

Cannot add directory

Adding a directory fails with (vendor\stechstudio\laravel-zipstream\src\Models\LocalFile.php:23):

fopen(/the/path/): failed to open stream: No such file or directory

I'm trying to add a directory without adding any files in it, i.e.

$zip->add('/path/to/dir/', 'dir/');

I just want an empty directory (no files, unless I later add files into it via add()). The idea is something like this:

$zip = new Zip('archive.zip');
$zip->add('/path/to/dir/', 'dir/');
if ($something) $zip->add('/path/to/dir/fle.jpg', 'dir/file.jpg');

I want the dir to exist even if no conditions are met. There doesn't seem to be an obvious way to accomplish this.

Non-ASCII filenames are being transliterated to ASCII using Str::ascii in src/Models/File.php class

Hi, @jszobody,

First of all, thank you so much for the great package. Always appreciate the hard work that you have put into it.

We have noticed that when creating zip, you are transliterating the filenames to ASCII using the Str::ascii method of Laravel's Str facade. So our file names that are in Arabic (non-ASCII characters) are transliterated to Ascii characters (which the Arabic speaking audience mostly don't understand)

We don't know the purpose behind it, perhaps you want to make the generated zip as much compatible as possible during extraction (since ZIPs don't carry encoding info with them).

I would like to know if it is possible to have this conversion optional e.g. in config files.

We would be grateful to make a PR if needed.

Method permalink:

public function getZipPath(): string

/**
     * @return string
     */
    public function getZipPath(): string
    {
        return Str::ascii(
            ltrim(
                preg_replace('|/{2,}|', '/',
                    $this->zipPath
                ),
                '/')
        );
    }

Enhancement: Comments to ZIP Files

Hello,

I'd like to see comments being able to be added to the ZIP files just to add a touch of customization. I don't have much to say so yeah. Comments!

Question: Can this library be used without Laravel Framework?

First of all, I think the maintainers have done an excellent job enhancing the original ZipStream library. Thanks for all your hard work!

Now, if I wanted to use this library in a non Laravel project (e.g. Drupal 8), what Laravel components would I need to require in my composer file? Your assistance is much appreciated.

Missing publish command in the documentation

Description

Trying to download files using the recommended setup code will produce an error (see this issue #24 for the log trace). The code assumes that some configuration are published, but the documentation misses from explaining this step.

Expected result

  • The documentation precise that we need to publish the configuration before using the package.
  • After publishing the configuration, the minimal working code does not throw an error related to #24.

Notes

I suggest to add this text before step 2. of the Quick Start section of the documentation:

2. Publish the vendor configuration

In your terminal, go in your folder and run this artisan command to publish the config/zipstream.php file:

php artisan vendor:publish --provider="STS\ZipStream\ZipStreamServiceProvider" --tag="config"

Zip not found

When I add this package through composer to a new Laravel 8 project, I get an error when trying to add the Zip::Create method. It cannot find the Zip namespace?

Did I forget something?

Using more than one set of credentials or storage locations s3 compatible

First, thank you for this git, it works perfect when pulling data from an s3 location.

I've been hacking the config and stream files to work in the following situation:

  1. We store all the files on s3 (one set of creds in the .env)
  2. After x number of days the files are transferred to our Minio cluster in the DC. (another set of creds in the .env)

The idea is that s3 if first checked with a try and catch exception then goes to the Minio file.

My only issue, is should I run a composer update, will I break my setup?

What do you suggest is the best way to pass the creds variables to be used in the Zip::create so that they are not broken on a future update?

Thanks!

filesize() error for comment in file

If a comment is written at the very beginning of a file (tried for a css and a js file), then you run into an filesize() error by trying to zip it. The comment is written like /*Comment*/

Filename is undefined and download does not start immediately

Hello,

first, thanks for this awesome library. It is exactly what I need right now.

The problem is: It does not work as expected. I am calling the Zip::create function in my controller like this:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Zip;

class DownloadController extends Controller
{
    public function getFileDownload(Request $request) {
        $filenames = $request->input('filenames');
        $mode = $request->input('mode');
        $username = $request->user()->getName();
        $filePaths = [];
        foreach($filenames as $filename) {
            array_push($filePaths, $this->getFilename($mode, $request->user()->getName(), $filename));
        }
        if ($filePaths) {
            return Zip::create("test.zip", $filePaths);
        }
        return response('Files not found', 404);
    }
}

On the client (latest stable Firefox and Chrome) no download is starting and the network activity shows a pending GET request to my API route. After a while a save Dialog appears which asks me to save a file called "undefined" declared application/octet-stream (1,1 GB) - Chrome downloads a "undefined.txt" instead. I expect that the download should start immediately (streamed download) and it should be of type zip - not octet-stream or txt, right?

If I download the file and rename it to .zip and I unzip it all files are there and everything works. The problem is that the streamed download, filename and content-type is not working as expected.

What can be the issue here?

How to handle exception on $zip->add

Aws \ S3 \ Exception \ S3Exception

Error executing "HeadObject" on ".....15/62-59-16490556511.jpeg"; AWS HTTP error: Client error: HEAD .....15/15/62-59-16490556511.jpeg resulted in a 404 Not Found response NotFound (client): 404 Not Found (Request-ID: ..........) -

Feature request urls

Thank you for the package.
I am trying to include a file from s3 in my zip. I noticed the link gets included as text. The thing is I only have access to a temporary url, which starts with http://, rather than s3://.
Would it be possible that File::make() autodetects URLs and streams them, rather than including them as textual content?
Since fopen() can be used for files and URLs I assume the UrlFile would look similar to LocalFile.

saveTo locally broken

An attempt to save the ZipStream to a non-existing file locally throws FilenameMissingException.
This is because a TmpFile is created without the zipPath being specified.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.