Skip to content

Commit

Permalink
Merge branch 'master' into findLabelShortcut
Browse files Browse the repository at this point in the history
-Resolve merge conflicts by integrating code changes from both branches.
  • Loading branch information
aneust committed Nov 6, 2024
2 parents 12ca1db + 058f487 commit d116854
Show file tree
Hide file tree
Showing 27 changed files with 936 additions and 62 deletions.
11 changes: 7 additions & 4 deletions app/Http/Controllers/Api/ProjectLabelTreeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ public function index($id)
}

/**
* Display all label trees that can be used by the specified project.
* Display label trees that match the given name and can be used by the specified project.
*
* @api {get} projects/:id/label-trees/available Get all available label trees
* @api {get} projects/:id/label-trees/available/:name Get matching label trees
* @apiGroup Projects
* @apiName IndexProjectAvailableLabelTrees
* @apiPermission projectMember
* @apiDescription This endpoint lists all label trees that _can be_ used by the project (do not confuse this with the "used label trees" endpoint).
* @apiDescription This endpoint lists all matching label trees that _can be_ used by the project (do not confuse this with the "used label trees" endpoint).
*
* @apiParam {Number} id The project ID.
*
Expand All @@ -86,19 +86,22 @@ public function index($id)
* ]
*
* @param int $id Project ID
* @param string $name Labeltree name
* @return array<int, LabelTree>
*/
public function available($id)
public function available($id, $name)
{
$project = Project::findOrFail($id);
$this->authorize('access', $project);

$public = LabelTree::publicTrees()
->select('id', 'name', 'description', 'version_id')
->where('name', 'ilike', "%{$name}%")
->with('version')
->get();
$authorized = $project->authorizedLabelTrees()
->select('id', 'name', 'description', 'version_id')
->where('name', 'ilike', "%{$name}%")
->with('version')
->get();

Expand Down
10 changes: 6 additions & 4 deletions app/Http/Controllers/Api/ProjectsAttachableVolumesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
class ProjectsAttachableVolumesController extends Controller
{
/**
* Shows all volumes that can be attached to the project by the requesting user.
* Shows volumes that match the given volume name and can be attached to the project by the requesting user.
*
* @api {get} projects/:id/attachable-volumes Get attachable volumes
* @api {get} projects/:id/attachable-volumes/:name Get matching attachable volumes
* @apiGroup Projects
* @apiName IndexAttachableVolumes
* @apiPermission projectAdmin
* @apiParam {Number} id ID of the project for which the volumes should be fetched.
* @apiDescription A list of all volumes where the requesting user has admin rights for (excluding those already belonging to the specified project).
* @apiDescription A list of all matching volumes where the requesting user has admin rights for (excluding those already belonging to the specified project).
*
* @apiSuccessExample {json} Success response:
* [
Expand All @@ -34,10 +34,11 @@ class ProjectsAttachableVolumesController extends Controller
*
* @param Request $request
* @param int $id Project ID
* @param string $name Volume name
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public function index(Request $request, $id)
public function index(Request $request, $id, $name)
{
$project = Project::findOrFail($id);
$this->authorize('update', $project);
Expand All @@ -56,6 +57,7 @@ public function index(Request $request, $id)
->where('project_id', '!=', $id);
});
})
->where('name', 'ilike', "%{$name}%")
// Do not return volumes that are already attached to this project.
// This is needed although we are already excluding the project in the
// previous statement because other projects may already share volumes with
Expand Down
101 changes: 101 additions & 0 deletions app/Jobs/CloneImageThumbnails.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Biigle\Jobs;

use Biigle\Image;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Storage;

class CloneImageThumbnails extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;

/**
* Path of original image thumbnails and tiled data
* @var string
*/
public $prefix;

/**
* Path of cloned image thumbnails and tiled data
* @var string
*/
public $copyPrefix;

/**
* Cloned image of cloned volume
* @var Image
*/
public $image;

public function __construct(Image $img, String $prefix)
{
$this->image = $img;
$this->prefix = $prefix;
$this->copyPrefix = fragment_uuid_path($img->uuid);
}

public function handle()
{
$diskThumb = Storage::disk(config('thumbnails.storage_disk'));
$diskTiles = Storage::disk(config('image.tiles.disk'));
$hasTiledImages = $this->hasTiledImages($diskTiles);

if (!$this->hasThumbnail($diskThumb) || $this->shouldBeTiled() && !$hasTiledImages) {
ProcessNewImage::dispatch($this->image);
return;
}
$format = config('thumbnails.format');
$diskThumb->copy($this->prefix.".{$format}", $this->copyPrefix.".{$format}");

if ($hasTiledImages) {
$files = $diskTiles->allFiles($this->prefix);
foreach ($files as $file) {
$fileName = str_replace("{$this->prefix}/", "", $file);
$diskTiles->copy($file, "{$this->copyPrefix}/{$fileName}");
}
}
}

/**
* Determine if original image has thumbnail.
*
* @param $disk containing original image's thumbnail.
*
* @return bool
*/
private function hasThumbnail($disk)
{
$format = config('thumbnails.format');
return $disk->exists("{$this->prefix}.{$format}");
}

/**
* Determine if original image has tiled data.
*
* @param $disk containing original image's tiled images.
*
* @return bool
*/
private function hasTiledImages($disk)
{
return $disk->exists("{$this->prefix}/ImageProperties.xml");
}

/**
* Determine if an image should be tiled.
*
* @return bool
*/
protected function shouldBeTiled()
{
if ($this->image->tiled && $this->image->tilingInProgress) {
return false;
}

$threshold = config('image.tiles.threshold');
return $this->image->width > $threshold || $this->image->height > $threshold;
}
}
11 changes: 10 additions & 1 deletion app/Jobs/CloneImagesOrVideos.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ class CloneImagesOrVideos extends Job implements ShouldQueue
**/
public array $onlyFileLabels;

/**
* Array mapping original image uuids to cloned image uuids
* @var array
*/
protected $uuidMap;

/**
* Ignore this job if the project or volume does not exist any more.
*
Expand All @@ -101,6 +107,7 @@ public function __construct($request, $copy)
$this->onlyAnnotationLabels = $request->input('only_annotation_labels', []);
$this->cloneFileLabels = $request->input('clone_file_labels', false);
$this->onlyFileLabels = $request->input('only_file_labels', []);
$this->uuidMap = [];

}

Expand Down Expand Up @@ -134,7 +141,7 @@ public function handle()
}
}
if ($copy->files()->exists()) {
ProcessNewVolumeFiles::dispatch($copy);
ProcessCloneVolumeFiles::dispatch($copy, $this->uuidMap, []);
}

if ($volume->hasMetadata()) {
Expand Down Expand Up @@ -166,6 +173,7 @@ private function copyImages($volume, $copy, $selectedImageIds)
$original = $image->getRawOriginal();
$original['volume_id'] = $copy->id;
$original['uuid'] = (string)Uuid::uuid4();
$this->uuidMap[$original['uuid']] = $image->uuid;
unset($original['id']);
return $original;
})
Expand Down Expand Up @@ -312,6 +320,7 @@ private function copyVideos($volume, $copy, $selectedVideoIds)
$original['volume_id'] = $copy->id;
$original['uuid'] = (string)Uuid::uuid4();
unset($original['id']);
$this->uuidMap[$original['uuid']] = $video->uuid;
return $original;
})
->chunk(1000)
Expand Down
82 changes: 82 additions & 0 deletions app/Jobs/CloneVideoThumbnails.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Biigle\Jobs;

use Biigle\Video;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Storage;

class CloneVideoThumbnails extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;

/**
* Path of original video thumbnails and sprites
* @var string
*/
public $prefix;

/**
* Path of cloned video thumbnails and sprites
* @var string
*/
public $copyPrefix;

/**
* Cloned video of cloned volume
* @var Video
*/
public $video;

public function __construct(Video $video, String $prefix)
{
$this->video = $video;
$this->prefix = $prefix;
$this->copyPrefix = fragment_uuid_path($video->uuid);
}

public function handle()
{
$disk = Storage::disk(config('videos.thumbnail_storage_disk'));

if (!$this->hasThumbnails($disk) || !$this->hasSprites($disk)) {
ProcessNewVideo::dispatch($this->video);
return;
}

$files = $disk->allFiles($this->prefix);
foreach ($files as $file) {
$fileName = str_replace("{$this->prefix}/", "", $file);
$disk->copy($file, "{$this->copyPrefix}/{$fileName}");
}
}

/**
* Determine if original video has thumbnails.
*
* @param $disk containing original video's thumbnails.
*
* @return bool
*/
private function hasThumbnails($disk)
{
$format = config('thumbnails.format');
return $disk->exists("{$this->prefix}/0.{$format}");
}

/**
* Determine if original video has sprites.
*
* @param $disk containing original video's sprites.
*
* @return bool
*/
private function hasSprites($disk)
{
$format = config('videos.sprites_format');
return $disk->exists("{$this->prefix}/sprite_0.{$format}");

}
}
87 changes: 87 additions & 0 deletions app/Jobs/ProcessCloneVolumeFiles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Biigle\Jobs;

use Biigle\Image;
use Biigle\Video;
use Biigle\Volume;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessCloneVolumeFiles extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;

/**
* The volume for which the files should be processed.
*
* @var Volume
*/
protected $volume;

/**
* Array of image/video IDs to restrict processing to.
* If it is empty, all files of the volume will be taken.
*
* @var array
*/
protected $only;

/**
* Array maps uuid of copied file to uuid of original files.
*
* @var array
*/
protected $uuidMap;

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

/**
* Create a new job instance.
*
* @param Volume $volume The volume for which the files should be processed.
* @param array $only (optional) Array of image/video IDs to restrict processing to.
* If it is empty, all files of the volume will be taken.
* @param array $uuidMap Array to map copied file uuid to the original file uuid during cloning process.
*
* @return void
*/
public function __construct(Volume $volume, array $uuidMap, array $only = [])
{
$this->volume = $volume;
$this->only = $only;
$this->uuidMap = $uuidMap;
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$query = $this->volume->files()
->when($this->only, fn ($query) => $query->whereIn('id', $this->only));

if ($this->volume->isImageVolume()) {
$query->eachById(function (Image $img) {
$prefix = fragment_uuid_path($this->uuidMap[$img->uuid]);
CloneImageThumbnails::dispatch($img, $prefix);
});
} else {
$queue = config('videos.process_new_video_queue');
$query->eachById(
function (Video $video) use ($queue) {
$prefix = fragment_uuid_path($this->uuidMap[$video->uuid]);
CloneVideoThumbnails::dispatch($video, $prefix)->onQueue($queue);
}
);
}
}
}
Loading

0 comments on commit d116854

Please sign in to comment.