Skip to content

Commit

Permalink
Refactor (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
aerni authored Dec 11, 2022
1 parent 4a0f0ca commit 6e4ff1b
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 162 deletions.
40 changes: 22 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ Somehwere in your views:

### Filename

You may optionally pass a filename using the `filename` parameter. If you don't provide one, the filename will default to the timestamp at the time of download. The example below binds the name of the zip to the title of the page.
You may optionally pass a filename using the `filename` parameter. The filename defaults to the timestamp when the Zip object was created. The example below binds the name of the zip to the title of the page.

```antlers
{{ zip:images :filename='title' }}
{{ zip:images :filename="title" }}
```

### Link Expiry
Expand All @@ -94,26 +94,30 @@ If you want to expire your links after a certain time, you can either set the ex

## Advanced Usage

This addon also exposes two methods that let you get the route or create a zip programmatically.

The `route` method returns the route that handles creating the zip. This is the same as using the `zip` tag in your views:
You may also use this addon programmatically as shown below.

```php
\Aerni\Zipper\Zipper::route($files, $filename, $expiry);
```

The `create` method creates and returns the zip directly:

```php
\Aerni\Zipper\Zipper::create($files, $filename);
```

The `$files` need to be a collection of assets, paths or URLs:
use Aerni\Zipper\Zip;

```php
$files = collect([
// Prepare an array of Statamic assets, paths or URLs.
$files = [
Statamic\Assets\Asset,
'/home/ploi/site.com/storage/app/assets/file_1.jpg',
'https://site.com/path/to/file_2.jpg',
])
];

// Make a zip with the files above.
$zip = Zip::make($files);

// Set an optional filename. This defaults to the timestamp when the object was created.
$zip->filename('obi-wan-kenobi')

// Set an optional expiry time in minutes. This defaults to the expiry set in the config.
$zip->expiry(60);

// Get the URL that handles creating the zip.
$zip->url();

// Create a new zip or download a previously cached zip.
$zip->get();
```
190 changes: 190 additions & 0 deletions src/Zip.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<?php

namespace Aerni\Zipper;

use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
use League\Flysystem\Local\LocalFilesystemAdapter;
use Statamic\Contracts\Assets\Asset;
use STS\ZipStream\Models\File;
use STS\ZipStream\ZipStream;
use STS\ZipStream\ZipStreamFacade;
use Symfony\Component\HttpFoundation\StreamedResponse;

class Zip
{
protected Collection $files;
protected string $filename;
protected int $expiry;

public function __construct(array $files)
{
$this
->files($files)
->filename(time())
->expiry((int) config('zipper.expiry'));
}

public static function make(array $files): self
{
return new self($files);
}

/**
* Get and set the files to zip.
*/
public function files(array $files = null): Collection|self
{
if (! func_get_args()) {
return $this->files;
}

$this->files = collect($files);

return $this;
}

/**
* Get and set the filename of the zip.
*/
public function filename(string $filename = null): string|self
{
if (! func_get_args()) {
return $this->filename;
}

// Make sure we never have an empty string as filename.
$this->filename = empty($filename) ? time() : $filename;

return $this;
}

/**
* Get and set the expiry of the zip route.
*/
public function expiry(int $expiry = null): int|self
{
if (! func_get_args()) {
return $this->expiry;
}

$this->expiry = $expiry;

return $this;
}

/**
* Returns the route that handles creating the zip.
*/
public function url(): string
{
if (empty($this->expiry)) {
return URL::signedRoute('statamic.zipper.create', Crypt::encrypt($this));
}

return URL::temporarySignedRoute(
'statamic.zipper.create',
now()->addMinutes($this->expiry),
Crypt::encrypt($this)
);
}

/**
* Create a new zip or download a previously cached zip.
*/
public function get(): ZipStream|StreamedResponse
{
return $this->shouldCacheZip() ? $this->cache() : $this->create();
}

/**
* Create and stream a new zip.
*/
protected function create(): ZipStream
{
$zip = ZipStreamFacade::create("{$this->filename}.zip");

$this->files->each(fn ($file) => $this->addFileToZip($file, $zip));

return $zip;
}

/**
* Stream the zip while also caching it to disk for future requests.
* This let's us download previously cached zips instead of creating new ones.
*/
protected function cache(): ZipStream|StreamedResponse
{
$zip = $this->create();
$filename = "{$zip->getFingerprint()}.zip";
$disk = Storage::disk(config('zipper.disk'));

if ($disk->exists($filename)) {
return $disk->download($filename, $zip->getName());
}

$adapter = $disk->getAdapter();

if ($adapter instanceof LocalFilesystemAdapter) {
return $zip->cache($disk->path($filename));
}

if ($adapter instanceof AwsS3V3Adapter) {
$path = "s3://{$disk->getConfig()['bucket']}/{$disk->path($filename)}";
$s3Client = $disk->getClient();
$file = File::make($path)->setS3Client($s3Client);

return $zip->cache($file);
}

throw new Exception('Zipper doesn\'t support ['.$adapter::class.'].');
}

/**
* Add a file to the zip.
*/
protected function addFileToZip(Asset|string $file, ZipStream $zip): ZipStream
{
if (is_string($file)) {
return $zip->add($file);
}

$disk = $file->disk()->filesystem();
$adapter = $disk->getAdapter();

if ($adapter instanceof LocalFilesystemAdapter) {
return $zip->add($file->resolvedPath());
}

if ($adapter instanceof AwsS3V3Adapter) {
$path = "s3://{$disk->getConfig()['bucket']}/{$file->path()}";
$s3Client = $disk->getClient();
$file = File::make($path)->setS3Client($s3Client);

return $zip->add($file);
}

throw new Exception('Zipper doesn\'t support ['.$adapter::class.'].');
}

/**
* The filename will be '0' if it isn't a timestamp.
*/
protected function hasCustomFilename(): bool
{
return (int) $this->filename === 0 ? true : false;
}

/**
* If the zip doesn't have a custom filename, we would be endlessly caching
* new zips and never returning previously cached zips.
*/
protected function shouldCacheZip(): bool
{
return config('zipper.save') && $this->hasCustomFilename();
}
}
129 changes: 0 additions & 129 deletions src/Zipper.php

This file was deleted.

Loading

0 comments on commit 6e4ff1b

Please sign in to comment.