Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize TileSingleImage #761

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/Console/Commands/MigrateTiledImages.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public function handle()

$query->eachById(function (Image $image) use ($dryRun, $bar, $disk) {
if (!$dryRun) {
Queue::push(new MigrateTiledImage($image, $disk));
$targetPath = fragment_uuid_path($image->uuid);
Queue::push(new MigrateTiledImage($image, $disk, $targetPath));
}
$bar->advance();
});
Expand Down
7 changes: 3 additions & 4 deletions app/Jobs/MigrateTiledImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class MigrateTiledImage extends TileSingleImage
*
* @return void
*/
public function __construct(Image $image, $disk)
public function __construct(Image $image, string $disk, string $targetPath)
{
parent::__construct($image);
parent::__construct($image, $disk, $targetPath);
$this->disk = $disk;
}

Expand All @@ -38,9 +38,8 @@ public function __construct(Image $image, $disk)
public function handle()
{
try {
$fragment = fragment_uuid_path($this->image->uuid);
$tmpResource = tmpfile();
$zipResource = Storage::disk($this->disk)->readStream($fragment);
$zipResource = Storage::disk($this->disk)->readStream($this->targetPath);
stream_copy_to_stream($zipResource, $tmpResource);
$zip = new ZipArchive;
$zip->open(stream_get_meta_data($tmpResource)['uri']);
Expand Down
3 changes: 2 additions & 1 deletion app/Jobs/ProcessNewImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@ protected function submitTileJob(Image $image)
$image->tiled = true;
$image->tilingInProgress = true;
$image->save();
TileSingleImage::dispatch($image);
$targetPath = fragment_uuid_path($image->uuid);
TileSingleImage::dispatch($image, config('image.tiles.disk'), $targetPath);
}

/**
Expand Down
116 changes: 14 additions & 102 deletions app/Jobs/TileSingleImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,32 @@
namespace Biigle\Jobs;

use Biigle\Image;
use Exception;
use File;
use File as FileFacade;
use FileCache;
use FilesystemIterator;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Jcupitt\Vips\Image as VipsImage;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

class TileSingleImage extends Job implements ShouldQueue
class TileSingleImage extends TileSingleObject
{
use InteractsWithQueue, SerializesModels;

/**
* The image to generate tiles for.
*
* @var Image
*/
public $image;

/**
* Path to the temporary storage file for the tiles.
*
* @var string
*/
public $tempPath;

/**
* Ignore this job if the image does not exist any more.
*
* @var bool
*/
protected $deleteWhenMissingModels = true;
public $file;

/**
* Create a new job instance.
*
* @param Image $image The image to generate tiles for.
* @param Image $file The image to generate tiles for.
* @param string $storage The path to storage-disk where the tiles should be stored
* @param string $targetPath The path to the tiles within the permanent storage-disk
*
* @return void
*/
public function __construct(Image $image)
public function __construct(Image $file, string $storage, string $targetPath)
{
$this->image = $image;
$this->tempPath = config('image.tiles.tmp_dir')."/{$image->uuid}";
parent::__construct($storage, $targetPath);
$this->file = $file;
$this->tempPath = config('image.tiles.tmp_dir')."/{$file->uuid}";
$this->queue = config('image.tiles.queue');
}

Expand All @@ -62,78 +40,12 @@ public function __construct(Image $image)
public function handle()
{
try {
FileCache::getOnce($this->image, [$this, 'generateTiles']);
FileCache::getOnce($this->file, [$this, 'generateTiles']);
$this->uploadToStorage();
$this->image->tilingInProgress = false;
$this->image->save();
$this->file->tilingInProgress = false;
$this->file->save();
} finally {
File::deleteDirectory($this->tempPath);
}
}

/**
* Generate tiles for the image and put them to temporary storage.
*
* @param Image $image
* @param string $path Path to the cached image file.
*/
public function generateTiles(Image $image, $path)
{
$this->getVipsImage($path)->dzsave($this->tempPath, [
'layout' => 'zoomify',
'container' => 'fs',
'strip' => true,
]);
}

/**
* Upload the tiles from temporary local storage to the tiles storage disk.
*/
public function uploadToStorage()
{
// +1 for the connecting slash.
$prefixLength = strlen($this->tempPath) + 1;
$iterator = $this->getIterator($this->tempPath);
$disk = Storage::disk(config('image.tiles.disk'));
$fragment = fragment_uuid_path($this->image->uuid);
try {
foreach ($iterator as $pathname => $fileInfo) {
$disk->putFileAs($fragment, $fileInfo, substr($pathname, $prefixLength));
}
} catch (Exception $e) {
$disk->deleteDirectory($fragment);
throw $e;
FileFacade::deleteDirectory($this->tempPath);
}
}

/**
* Get the vips image instance.
*
* @param string $path
*
* @return \Jcupitt\Vips\Image
*/
protected function getVipsImage($path)
{
return VipsImage::newFromFile($path, ['access' => 'sequential']);
}

/**
* Get the recursive directory iterator for the given path.
*
* @param string $path
*
* @return RecursiveIteratorIterator
*/
protected function getIterator($path)
{
return new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$path,
FilesystemIterator::KEY_AS_PATHNAME |
FilesystemIterator::CURRENT_AS_FILEINFO |
FilesystemIterator::SKIP_DOTS
)
);
}
}
133 changes: 133 additions & 0 deletions app/Jobs/TileSingleObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

namespace Biigle\Jobs;

use Biigle\FileCache\Contracts\File;
use Exception;
use FilesystemIterator;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Jcupitt\Vips\Image as VipsImage;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

abstract class TileSingleObject extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;

/**
* Path to the temporary storage file for the tiles.
*
* @var string
*/
public $tempPath;

/**
* The path of the permanent storage-disk where the tiles should be stored.
*
* @var string
*/
public $storage;

/**
* Path to the tiles within the permanent storage-disk.
*
* @var string
*/
public $targetPath;

/**
* Ignore this job if the image does not exist any more.
*
* @var bool
*/
protected $deleteWhenMissingModels = true;

/**
* Create a new job instance.
*
* @param string $storage The path to storage-disk where the tiles should be stored
* @param string $targetPath The path to the tiles within the permanent storage-disk
*
* @return void
*/
public function __construct(string $storage, string $targetPath)
{
$this->storage = $storage;
$this->targetPath = $targetPath;
}

/**
* Execute the job.
*
* @return void
*/
abstract protected function handle();

/**
* Generate tiles for the object and put them to temporary storage.
*
* @param File $file
* @param string $path Path to the cached image file.
*/
public function generateTiles($file, $path)
{
$this->getVipsImage($path)->dzsave($this->tempPath, [
'layout' => 'zoomify',
'container' => 'fs',
'strip' => true,
]);
}

/**
* Upload the tiles from temporary local storage to the tiles storage disk.
*/
public function uploadToStorage()
{
// +1 for the connecting slash.
$prefixLength = strlen($this->tempPath) + 1;
$iterator = $this->getIterator($this->tempPath);
$disk = Storage::disk($this->storage);
try {
foreach ($iterator as $pathname => $fileInfo) {
$disk->putFileAs($this->targetPath, $fileInfo, substr($pathname, $prefixLength));
}
} catch (Exception $e) {
$disk->deleteDirectory($this->targetPath);
throw $e;
}
}

/**
* Get the vips image instance.
*
* @param string $path
*
* @return \Jcupitt\Vips\Image
*/
protected function getVipsImage($path)
{
return VipsImage::newFromFile($path, ['access' => 'sequential']);
}

/**
* Get the recursive directory iterator for the given path.
*
* @param string $path
*
* @return RecursiveIteratorIterator
*/
protected function getIterator($path)
{
return new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$path,
FilesystemIterator::KEY_AS_PATHNAME |
FilesystemIterator::CURRENT_AS_FILEINFO |
FilesystemIterator::SKIP_DOTS
)
);
}
}
2 changes: 1 addition & 1 deletion tests/php/Jobs/ProcessNewImageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public function testHandleTileLargeImage()
Queue::fake();
$job->handle();

Queue::assertPushed(TileSingleImage::class, fn ($job) => $job->image->id === $image->id);
Queue::assertPushed(TileSingleImage::class, fn ($job) => $job->file->id === $image->id);
$image->refresh();
$this->assertTrue($image->tiled);
$this->assertTrue($image->tilingInProgress);
Expand Down
32 changes: 26 additions & 6 deletions tests/php/Jobs/TileSingleImageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ class TileSingleImageTest extends TestCase
public function testGenerateTiles()
{
$image = ImageTest::create();
$job = new TileSingleImageStub($image);
Storage::fake('tiles');
$targetPath = fragment_uuid_path($image->uuid);
$job = new TileSingleImageStub($image, config('image.tiles.disk'), $targetPath);

$mock = Mockery::mock(Image::class);
$mock->shouldReceive('dzsave')
Expand All @@ -35,26 +37,44 @@ public function testUploadToStorage()
{
config(['image.tiles.disk' => 'tiles']);
$image = ImageTest::create();
$fragment = fragment_uuid_path($image->uuid);
$job = new TileSingleImageStub($image);
$targetPath = fragment_uuid_path($image->uuid);
$job = new TileSingleImageStub($image, config('image.tiles.disk'), $targetPath);
File::makeDirectory($job->tempPath);
File::put("{$job->tempPath}/test.txt", 'test');

try {
Storage::fake('tiles');
$job->uploadToStorage();
Storage::disk('tiles')->assertExists($fragment);
Storage::disk('tiles')->assertExists("{$fragment}/test.txt");
Storage::disk('tiles')->assertExists($targetPath);
Storage::disk('tiles')->assertExists("{$targetPath}/test.txt");
} finally {
File::deleteDirectory($job->tempPath);
}
}

public function testHandleFunction()
{
$image = ImageTest::create();
$image->tilingInProgress = true;
$targetPath = fragment_uuid_path($image->uuid);
$job = new TileSingleImage($image, config('image.tiles.disk'), $targetPath);

$this->assertEquals($job->file->tilingInProgress, true);
Storage::fake('tiles');
// execute the job with handle() method
$job->handle();
$this->assertEquals($job->file->tilingInProgress, false);
Storage::disk('tiles')->assertExists($targetPath);
// check that temporary storage path got properly deleted in handle() method
Storage::disk('tiles')->assertMissing($job->tempPath);
}

public function testQueue()
{
config(['image.tiles.queue' => 'myqueue']);
$image = ImageTest::create();
$job = new TileSingleImageStub($image);
$targetPath = fragment_uuid_path($image->uuid);
$job = new TileSingleImageStub($image, config('image.tiles.disk'), $targetPath);
$this->assertSame('myqueue', $job->queue);
}
}
Expand Down