Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 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
10 changes: 7 additions & 3 deletions .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ jobs:
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: 8.4
extensions: opentelemetry

- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -119,6 +120,7 @@ jobs:
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: 8.4
extensions: opentelemetry

- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -148,6 +150,7 @@ jobs:
with:
php-version: 8.4
coverage: none
extensions: opentelemetry

- name: Install Composer dependencies
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0
Expand All @@ -174,6 +177,7 @@ jobs:
with:
php-version: 8.4
coverage: none
extensions: opentelemetry

- name: Install Composer dependencies
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0
Expand Down Expand Up @@ -217,7 +221,7 @@ jobs:
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: 8.4
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, sqlite3, zip
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, sqlite3, zip, opentelemetry
coverage: xdebug
tools: pecl, composer

Expand Down Expand Up @@ -479,7 +483,7 @@ jobs:
id-token: write
attestations: write
env:
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql, pgsql, sqlite3, zip
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql, pgsql, sqlite3, zip, opentelemetry

steps:
- name: Harden Runner
Expand Down Expand Up @@ -555,7 +559,7 @@ jobs:
contents: write
id-token: write
env:
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql, pgsql, sqlite3, zip
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql, pgsql, sqlite3, zip, opentelemetry

steps:
- name: Install Cosign
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/php_dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
env:
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql, pgsql, sqlite3, zip
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql, pgsql, sqlite3, zip, opentelemetry

steps:
- name: Harden Runner
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/php_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
env:
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql, pgsql, sqlite3, zip
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql, pgsql, sqlite3, zip, opentelemetry

steps:
- name: Harden Runner
Expand Down
174 changes: 92 additions & 82 deletions app/Actions/Photo/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use App\Models\Photo;
use App\Models\User;
use App\Services\Image\FileExtensionService;
use App\Services\Telemetry\TraceService;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Pipeline\Pipeline;
use LycheeVerify\Contract\VerifyInterface;
Expand All @@ -42,6 +43,8 @@ class Create
/** @var ImportParam the strategy parameters prepared and compiled by this class */
protected ImportParam $strategy_parameters;

private TraceService $trace;

public function __construct(
?ImportMode $import_mode,
int $intended_owner_id,
Expand All @@ -58,6 +61,7 @@ public function __construct(
preallocated_id: $preallocated_id,
upload_trust_level: $upload_trust_level,
);
$this->trace = app(TraceService::class);
}

/**
Expand All @@ -81,59 +85,63 @@ public function __construct(
*/
public function add(NativeLocalFile $source_file, ?AbstractAlbum $album, ?int $file_last_modified_time = null): Photo
{
$this->checkQuota($source_file);

/** @var InitDTO $init_dto */
$init_dto = new InitDTO(
parameters: $this->strategy_parameters,
source_file: $source_file,
album: $album,
file_last_modified_time: $file_last_modified_time
);

$pre_pipes = [
Init\DetectAndStoreRaw::class,
Init\AssertSupportedMedia::class,
Init\FetchLastModifiedTime::class,
Init\MayLoadFileMetadata::class,
Init\FindDuplicate::class,
];

$init_dto = app(Pipeline::class)
->send($init_dto)
->through($pre_pipes)
->thenReturn();
return $this->trace->traceMethod('photo.create.add', function () use ($source_file,
$album,
$file_last_modified_time) {
$this->checkQuota($source_file);

/** @var InitDTO $init_dto */
$init_dto = new InitDTO(
parameters: $this->strategy_parameters,
source_file: $source_file,
album: $album,
file_last_modified_time: $file_last_modified_time
);

$pre_pipes = [
Init\DetectAndStoreRaw::class,
Init\AssertSupportedMedia::class,
Init\FetchLastModifiedTime::class,
Init\MayLoadFileMetadata::class,
Init\FindDuplicate::class,
];

$init_dto = app(Pipeline::class)
->send($init_dto)
->through($pre_pipes)
->thenReturn();

if ($init_dto->duplicate !== null) {
return $this->handleDuplicate($init_dto);
}
if ($init_dto->duplicate !== null) {
return $this->handleDuplicate($init_dto);
}

$post_pipes = [
Init\InitParentAlbum::class,
Init\LoadFileMetadata::class,
Init\FindLivePartner::class,
];
$post_pipes = [
Init\InitParentAlbum::class,
Init\LoadFileMetadata::class,
Init\FindLivePartner::class,
];

$init_dto = app(Pipeline::class)
->send($init_dto)
->through($post_pipes)
->thenReturn();
$init_dto = app(Pipeline::class)
->send($init_dto)
->through($post_pipes)
->thenReturn();

if ($init_dto->live_partner === null) {
return $this->handleStandalone($init_dto);
}
if ($init_dto->live_partner === null) {
return $this->handleStandalone($init_dto);
}

// livePartner !== null
$file_extension_service = app(FileExtensionService::class);
if ($file_extension_service->isSupportedVideo($source_file->getMimeType(), $source_file->getOriginalExtension())) {
return $this->handleVideoLivePartner($init_dto);
}
// livePartner !== null
$file_extension_service = app(FileExtensionService::class);
if ($file_extension_service->isSupportedVideo($source_file->getMimeType(), $source_file->getOriginalExtension())) {
return $this->handleVideoLivePartner($init_dto);
}

if ($file_extension_service->isSupportedImage($source_file->getPath(), $source_file->getMimeType(), $source_file->getOriginalExtension())) {
return $this->handlePhotoLivePartner($init_dto);
}
if ($file_extension_service->isSupportedImage($source_file->getPath(), $source_file->getMimeType(), $source_file->getOriginalExtension())) {
return $this->handlePhotoLivePartner($init_dto);
}

throw new LycheeLogicException('Pipe system for importing video failed');
throw new LycheeLogicException('Pipe system for importing video failed');
});
}

/**
Expand Down Expand Up @@ -176,41 +184,43 @@ private function handleDuplicate(InitDTO $init_dto): Photo

private function handleStandalone(InitDTO $init_dto): Photo
{
$dto = StandaloneDTO::ofInit($init_dto);

$pipes = [
Standalone\FixTimeStamps::class,
Standalone\InitNamingStrategy::class,
Standalone\ApplyUserProvidedMetadata::class,
Shared\HydrateMetadata::class,
Shared\SetHighlighted::class,
Shared\SetOwnership::class,
Standalone\SetOriginalChecksum::class,
Standalone\FetchSourceImage::class,
Standalone\ExtractGoogleMotionPictures::class,
Standalone\PlacePhoto::class,
Standalone\PlaceGoogleMotionVideo::class,
Standalone\SetChecksum::class,
Standalone\AutoRenamer::class,
Shared\SetUploadValidated::class,
Shared\Save::class,
Shared\SetParent::class,
Shared\SaveStatistics::class,
Standalone\CreateOriginalSizeVariant::class,
Standalone\CreateRawSizeVariant::class,
Standalone\CreateSizeVariants::class,
Standalone\ApplyWatermark::class,
Standalone\EncodePlaceholder::class,
Standalone\ReplaceOriginalWithBackup::class,
Shared\UploadSizeVariantsToS3::class,
Shared\GeodecodeLocation::class,
Shared\ExtractColourPalette::class,
Shared\NotifyAlbums::class,
Standalone\AutoScanFacesOnUpload::class,
Standalone\AutoScanNsfwOnUpload::class,
];

return $this->executePipeOnDTO($pipes, $dto)->getPhoto();
return $this->trace->traceMethod('photo.handle_standalone', function () use ($init_dto) {
$dto = StandaloneDTO::ofInit($init_dto);

$pipes = [
Standalone\FixTimeStamps::class,
Standalone\InitNamingStrategy::class,
Standalone\ApplyUserProvidedMetadata::class,
Shared\HydrateMetadata::class,
Shared\SetHighlighted::class,
Shared\SetOwnership::class,
Standalone\SetOriginalChecksum::class,
Standalone\FetchSourceImage::class,
Standalone\ExtractGoogleMotionPictures::class,
Standalone\PlacePhoto::class,
Standalone\PlaceGoogleMotionVideo::class,
Standalone\SetChecksum::class,
Standalone\AutoRenamer::class,
Shared\SetUploadValidated::class,
Shared\Save::class,
Shared\SetParent::class,
Shared\SaveStatistics::class,
Standalone\CreateOriginalSizeVariant::class,
Standalone\CreateRawSizeVariant::class,
Standalone\CreateSizeVariants::class,
Standalone\ApplyWatermark::class,
Standalone\EncodePlaceholder::class,
Standalone\ReplaceOriginalWithBackup::class,
Shared\UploadSizeVariantsToS3::class,
Shared\GeodecodeLocation::class,
Shared\ExtractColourPalette::class,
Shared\NotifyAlbums::class,
Standalone\AutoScanFacesOnUpload::class,
Standalone\AutoScanNsfwOnUpload::class,
];

return $this->executePipeOnDTO($pipes, $dto)->getPhoto();
});
}

private function handleVideoLivePartner(InitDTO $init_dto): Photo
Expand Down Expand Up @@ -378,4 +388,4 @@ private function checkQuota(NativeLocalFile $source_file): void
throw new QuotaExceededException();
}
}
}
}
39 changes: 22 additions & 17 deletions app/Actions/Photo/Pipes/Init/LoadFileMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use App\DTO\PhotoCreate\InitDTO;
use App\Exceptions\InvalidPropertyException;
use App\Metadata\Extractor;
use App\Services\Telemetry\TraceService;

/**
* Load metadata from the file.
Expand All @@ -25,27 +26,31 @@ class LoadFileMetadata implements InitPipe
*/
public function handle(InitDTO $state, \Closure $next): InitDTO
{
if ($state->exif_info !== null) {
// Metadata already loaded
return $next($state);
}
$trace = app(TraceService::class);

return $trace->traceMethod('photo.load_file_metadata', function () use ($state, $next) {
Comment thread
FredPraca marked this conversation as resolved.
Outdated
if ($state->exif_info !== null) {
// Metadata already loaded
return $next($state);
}

// When a RAW source is available (e.g. CR3, NEF, HEIC) prefer it for
// metadata extraction because the converted JPEG may lose EXIF data.
$metadata_file = $state->raw_source_file ?? $state->source_file;
// When a RAW source is available (e.g. CR3, NEF, HEIC) prefer it for
// metadata extraction because the converted JPEG may lose EXIF data.
$metadata_file = $state->raw_source_file ?? $state->source_file;

$state->exif_info = Extractor::createFromFile($metadata_file, $state->file_last_modified_time);
$state->exif_info = Extractor::createFromFile($metadata_file, $state->file_last_modified_time);

// Use basename of the original upload for the title, not the converted file
if (
$state->exif_info->title === null ||
$state->exif_info->title === ''
) {
$title_source = $state->raw_source_file ?? $state->source_file;
$state->exif_info->title = mb_substr($title_source->getOriginalBasename(), 0, 100, 'UTF-8');
}
// Use basename of the original upload for the title, not the converted file
if (
$state->exif_info->title === null ||
$state->exif_info->title === ''
) {
$title_source = $state->raw_source_file ?? $state->source_file;
$state->exif_info->title = mb_substr($title_source->getOriginalBasename(), 0, 100, 'UTF-8');
}

return $next($state);
return $next($state);
});
}
}

35 changes: 35 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/AbstractStandalonePipe.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/**
* SPDX-License-Identifier: MIT
* Copyright (c) 2017-2018 Tobias Reich
* Copyright (c) 2018-2026 LycheeOrg.
*/

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Services\Telemetry\TraceService;

abstract class AbstractStandalonePipe implements StandalonePipe
{
/**
* @param StandaloneDTO $state
* @param \Closure(StandaloneDTO $state): StandaloneDTO $next
*
* @return StandaloneDTO
*/
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
$trace = app(TraceService::class);

return $trace->traceMethod($this->getSpanName(), function () use ($state, $next) {
return $this->execute($state, $next);
});
Comment thread
FredPraca marked this conversation as resolved.
Outdated
}

abstract protected function getSpanName(): string;

abstract protected function execute(StandaloneDTO $state, \Closure $next): StandaloneDTO;
}
Loading
Loading