From 60d95cd1f5b41bace665feb3dbeafaf896832a8e Mon Sep 17 00:00:00 2001 From: Michael Aerni Date: Tue, 11 Apr 2023 15:48:20 -0400 Subject: [PATCH] Delete old reference files (#15) --- README.md | 42 +++++++++++++++++++++ config/zipper.php | 16 ++++++++ src/Commands/CleanReferenceFilesCommand.php | 23 +++++++++++ src/Jobs/CleanReferenceFilesJob.php | 36 ++++++++++++++++++ src/ServiceProvider.php | 12 ++++++ src/Zip.php | 28 ++++++++++++-- src/ZipperStore.php | 20 ++++++++++ 7 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 src/Commands/CleanReferenceFilesCommand.php create mode 100644 src/Jobs/CleanReferenceFilesJob.php diff --git a/README.md b/README.md index d5cc3ad..65a3fe3 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,22 @@ return [ 'expiry' => null, + /* + |-------------------------------------------------------------------------- + | Cleanup Scope + |-------------------------------------------------------------------------- + | + | The scope to use when cleaning up your zip references with the scheduled command. + | + | Options: + | "expired": Only delete expired reference files + | "all": Delete all reference files excluding unexpired files + | "force": Delete all reference files including unexpired files + | + */ + + 'cleanup' => 'expired', + ]; ``` @@ -92,6 +108,32 @@ If you want to expire your links after a certain time, you can either set the ex {{ zip:images expiry="60" }} ``` +## Cleanup Old References + +Zipper saves an encrypted instance of the Zip class every time it returns a URL. These reference files are stored in `storage/zipper/{id}`. Whenever a user downloads a zip, Zipper will retrieve and decrypt the requested Zip instance. + +With time, the amound of saved reference files will grow. To get this under control, Zipper provides a scheduled command that will daily delete old reference files. Just make sure that your Scheduler is running. + +### Cleanup Scopes + +There are a couple of cleanup scopes you can define in the config: + +| Option | Description | +|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `expired` | Only delete expired references files. This only affects references of zips that used the `expiry` option | +| `all` | Delete all reference files excluding unexpired files. This will delete references of zips that didn't use the expiry option as well as expired zips. It will not delete unexpired zips. | +| `force` | Delete all reference files including unexpired files. This will completely wipe all references. | + +### Clean Command + +You may also use the `clean` command to delete reference files at your will. The scope defaults to `expired`. + +```bash +php please zipper:clean +php please zipper:clean --scope=all +php please zipper:clean --scope=force +``` + ## Advanced Usage You may also use this addon programmatically as shown below. diff --git a/config/zipper.php b/config/zipper.php index ff2424e..2a92596 100644 --- a/config/zipper.php +++ b/config/zipper.php @@ -36,4 +36,20 @@ 'expiry' => null, + /* + |-------------------------------------------------------------------------- + | Cleanup Scope + |-------------------------------------------------------------------------- + | + | The scope to use when cleaning up your zip references with the scheduled command. + | + | Options: + | "expired": Only delete expired reference files + | "all": Delete all reference files excluding unexpired files + | "force": Delete all reference files including unexpired files + | + */ + + 'cleanup' => 'expired', + ]; diff --git a/src/Commands/CleanReferenceFilesCommand.php b/src/Commands/CleanReferenceFilesCommand.php new file mode 100644 index 0000000..33202dc --- /dev/null +++ b/src/Commands/CleanReferenceFilesCommand.php @@ -0,0 +1,23 @@ +option('scope')); + + $this->info('Successfully dispatched the cleanup job.'); + } +} diff --git a/src/Jobs/CleanReferenceFilesJob.php b/src/Jobs/CleanReferenceFilesJob.php new file mode 100644 index 0000000..d4ee5df --- /dev/null +++ b/src/Jobs/CleanReferenceFilesJob.php @@ -0,0 +1,36 @@ +scope === 'expired') => $zips->filter(fn ($zip) => $zip->expired()), // Only delete expired reference files + ($this->scope === 'all') => $zips->filter(fn ($zip) => $zip->expired() || empty($zip->expiry())), // Delete all reference files excluding unexpired files + ($this->scope === 'force') => $zips, // Delete all reference files including unexpired files + default => throw new \Exception('Please provide a valid cleanup scope.') + }; + + $zips->each(fn ($zip) => $zip->deleteReferenceFile()); + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 1bf47e9..98f7b8a 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,10 +2,15 @@ namespace Aerni\Zipper; +use Aerni\Zipper\Commands\CleanReferenceFilesCommand; use Statamic\Providers\AddonServiceProvider; class ServiceProvider extends AddonServiceProvider { + protected $commands = [ + CleanReferenceFilesCommand::class, + ]; + protected $routes = [ 'actions' => __DIR__.'/../routes/actions.php', ]; @@ -13,4 +18,11 @@ class ServiceProvider extends AddonServiceProvider protected $tags = [ ZipperTags::class, ]; + + protected function schedule($schedule) + { + $scope = config('zipper.cleanup', 'expired'); + + $schedule->command(CleanReferenceFilesCommand::class, ["--scope={$scope}"])->daily(); + } } diff --git a/src/Zip.php b/src/Zip.php index 7aafb82..9c5134e 100644 --- a/src/Zip.php +++ b/src/Zip.php @@ -77,6 +77,20 @@ public function expiry(int $expiry = null): int|self return $this; } + /** + * Check if the stored zip reference file is expired. + */ + public function expired(): bool + { + if (empty($this->expiry)) { + return false; + } + + return ZipperStore::createdAt($this->id()) + ->addMinutes($this->expiry) + ->isPast(); + } + /** * Returns the route that handles creating the zip. */ @@ -94,9 +108,17 @@ public function url(): string */ protected function storeReferenceFile(): self { - if (! ZipperStore::exists($this->id())) { - ZipperStore::put($this->id(), $this); - } + ZipperStore::put($this->id(), $this); + + return $this; + } + + /** + * Delete the zip reference file. + */ + public function deleteReferenceFile(): self + { + ZipperStore::delete($this->id()); return $this; } diff --git a/src/ZipperStore.php b/src/ZipperStore.php index 159200a..f8f10f9 100644 --- a/src/ZipperStore.php +++ b/src/ZipperStore.php @@ -3,6 +3,8 @@ namespace Aerni\Zipper; use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Support\Carbon; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Storage; @@ -18,6 +20,12 @@ public function __construct() ]); } + public function all(): Collection + { + return collect($this->store->allFiles()) + ->map(fn ($file) => $this->get($file)); + } + public function put(string $path, Zip $zip): bool { return $this->store->put($path, Crypt::encrypt($zip)); @@ -36,4 +44,16 @@ public function exists(string $path): bool { return $this->store->exists($path); } + + public function delete(string $path): bool + { + return $this->store->delete($path); + } + + public function createdAt(string $path): Carbon + { + $createdAt = filemtime(storage_path('statamic/zipper').'/'.$path); + + return Carbon::createFromTimestamp($createdAt); + } }