Skip to content

Commit

Permalink
[stacked 5] Add initial steps as Pipes (LycheeOrg#2368)
Browse files Browse the repository at this point in the history
  • Loading branch information
ildyria committed Apr 11, 2024
1 parent 1b1f1ae commit fdee82c
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 0 deletions.
30 changes: 30 additions & 0 deletions app/Actions/Photo/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

namespace App\Actions\Photo;

use App\Actions\Photo\Pipes\Init;
use App\Assets\Features;
use App\Contracts\Exceptions\LycheeException;
use App\Contracts\Models\AbstractAlbum;
use App\DTO\ImportMode;
use App\DTO\ImportParam;
use App\DTO\PhotoCreate\InitDTO;
use App\Image\Files\NativeLocalFile;
use App\Legacy\Actions\Photo\Create as LegacyPhotoCreate;
use App\Models\Photo;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Pipeline\Pipeline;

class Create
{
Expand Down Expand Up @@ -41,6 +45,32 @@ public function __construct(?ImportMode $importMode, int $intendedOwnerId)
*/
public function add(NativeLocalFile $sourceFile, ?AbstractAlbum $album, ?int $fileLastModifiedTime = null): Photo
{
if (Features::inactive('create-photo-via-pipes')) {
$oldCodePath = new LegacyPhotoCreate($this->strategyParameters->importMode, $this->strategyParameters->intendedOwnerId);

return $oldCodePath->add($sourceFile, $album, $fileLastModifiedTime);
}

$initDTO = new InitDTO(
parameters: $this->strategyParameters,
sourceFile: $sourceFile,
album: $album,
fileLastModifiedTime: $fileLastModifiedTime
);

/** @var InitDTO $initDTO */
$initDTO = app(Pipeline::class)
->send($initDTO)
->through([
Init\AssertSupportedMedia::class,
Init\FetchLastModifiedTime::class,
Init\InitParentAlbum::class,
Init\LoadFileMetadata::class,
Init\FindDuplicate::class,
Init\FindLivePartner::class,
])
->thenReturn();

$oldCodePath = new LegacyPhotoCreate($this->strategyParameters->importMode, $this->strategyParameters->intendedOwnerId);

return $oldCodePath->add($sourceFile, $album, $fileLastModifiedTime);
Expand Down
27 changes: 27 additions & 0 deletions app/Actions/Photo/Pipes/Init/AssertSupportedMedia.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace App\Actions\Photo\Pipes\Init;

use App\Contracts\PhotoCreate\InitPipe;
use App\DTO\PhotoCreate\InitDTO;
use App\Exceptions\MediaFileOperationException;
use App\Exceptions\MediaFileUnsupportedException;

/**
* Assert whether we support said file.
*/
class AssertSupportedMedia implements InitPipe
{
/**
* {@inheritDoc}
*
* @throws MediaFileUnsupportedException
* @throws MediaFileOperationException
*/
public function handle(InitDTO $state, \Closure $next): InitDTO
{
$state->sourceFile->assertIsSupportedMediaOrAcceptedRaw();

return $next($state);
}
}
25 changes: 25 additions & 0 deletions app/Actions/Photo/Pipes/Init/FetchLastModifiedTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Actions\Photo\Pipes\Init;

use App\Contracts\PhotoCreate\InitPipe;
use App\DTO\PhotoCreate\InitDTO;

/**
* Set fileLastModifiedTime if null.
*/
class FetchLastModifiedTime implements InitPipe
{
/**
* {@inheritDoc}
*/
public function handle(InitDTO $state, \Closure $next): InitDTO
{
if ($state->fileLastModifiedTime === null) {
$state->fileLastModifiedTime ??= $state->sourceFile->lastModified();
}

return $next($state);
}
}

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

namespace App\Actions\Photo\Pipes\Init;

use App\Contracts\PhotoCreate\InitPipe;
use App\DTO\PhotoCreate\InitDTO;
use App\Image\StreamStat;
use App\Models\Photo;

/**
* Look for duplicates of the file in the database.
*/
class FindDuplicate implements InitPipe
{
/**
* {@inheritDoc}
*/
public function handle(InitDTO $state, \Closure $next): InitDTO
{
$checksum = StreamStat::createFromLocalFile($state->sourceFile)->checksum;

$state->duplicate = Photo::query()
->where('checksum', '=', $checksum)
->orWhere('original_checksum', '=', $checksum)
->orWhere('live_photo_checksum', '=', $checksum)
->first();

return $next($state);
}
}

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

namespace App\Actions\Photo\Pipes\Init;

use App\Contracts\PhotoCreate\InitPipe;
use App\DTO\PhotoCreate\InitDTO;
use App\Exceptions\Internal\IllegalOrderOfOperationException;
use App\Exceptions\Internal\LycheeAssertionError;
use App\Image\Files\BaseMediaFile;
use App\Models\Photo;

/**
* Try to link live photo components together.
*/
class FindLivePartner implements InitPipe
{
/**
* {@inheritDoc}
*/
public function handle(InitDTO $state, \Closure $next): InitDTO
{
try {
// find a potential partner which has the same content id
if ($state->exifInfo->livePhotoContentID !== null) {
$state->livePartner = Photo::query()
->where('live_photo_content_id', '=', $state->exifInfo->livePhotoContentID)
->where('album_id', '=', $state->album?->id)
->whereNull('live_photo_short_path')->first();
}

// if a potential partner has been found, ensure that it is of a
// different kind then the uploaded media.
if (
$state->livePartner !== null && !(
BaseMediaFile::isSupportedImageMimeType($state->exifInfo->type) && $state->livePartner->isVideo() ||
BaseMediaFile::isSupportedVideoMimeType($state->exifInfo->type) && $state->livePartner->isPhoto()
)
) {
$state->livePartner = null;
}

return $next($state);
} catch (IllegalOrderOfOperationException $e) {
throw LycheeAssertionError::createFromUnexpectedException($e);
}
}
}

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

namespace App\Actions\Photo\Pipes\Init;

use App\Contracts\PhotoCreate\InitPipe;
use App\DTO\PhotoCreate\InitDTO;
use App\Exceptions\InvalidPropertyException;
use App\Models\Album;
use App\SmartAlbums\BaseSmartAlbum;
use App\SmartAlbums\StarredAlbum;

/**
* Init album.
*/
class InitParentAlbum implements InitPipe
{
/**
* {@inheritDoc}
*
* @throws InvalidPropertyException
*/
public function handle(InitDTO $state, \Closure $next): InitDTO
{
if ($state->album === null || $state->album instanceof Album) {
return $next($state);
}

if ($state->album instanceof BaseSmartAlbum) {
if ($state->album instanceof StarredAlbum) {
$state->is_starred = true;
}

$state->album = null;

return $next($state);
}

throw new InvalidPropertyException('The given parent album does not support uploading');
}
}
35 changes: 35 additions & 0 deletions app/Actions/Photo/Pipes/Init/LoadFileMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Actions\Photo\Pipes\Init;

use App\Contracts\PhotoCreate\InitPipe;
use App\DTO\PhotoCreate\InitDTO;
use App\Exceptions\InvalidPropertyException;
use App\Metadata\Extractor;

/**
* Load metadata from the file.
*/
class LoadFileMetadata implements InitPipe
{
/**
* {@inheritDoc}
*
* @throws InvalidPropertyException
*/
public function handle(InitDTO $state, \Closure $next): InitDTO
{
$state->exifInfo = Extractor::createFromFile($state->sourceFile, $state->fileLastModifiedTime);

// Use basename of file if IPTC title missing
if (
$state->exifInfo->title === null ||
$state->exifInfo->title === ''
) {
$state->exifInfo->title = substr($state->sourceFile->getOriginalBasename(), 0, 98);
}

return $next($state);
}
}

21 changes: 21 additions & 0 deletions app/Contracts/PhotoCreate/InitPipe.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Contracts\PhotoCreate;

use App\DTO\PhotoCreate\InitDTO;

/**
* Basic definition of a Photo creation pipe.
*
* This allows to clarify which steps are applied in which order.
*/
interface InitPipe
{
/**
* @param InitDTO $state
* @param \Closure(InitDTO $state): InitDTO $next
*
* @return InitDTO
*/
public function handle(InitDTO $state, \Closure $next): InitDTO;
}
55 changes: 55 additions & 0 deletions app/DTO/PhotoCreate/InitDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace App\DTO\PhotoCreate;

use App\Contracts\Models\AbstractAlbum;
use App\DTO\ImportMode;
use App\DTO\ImportParam;
use App\Image\Files\NativeLocalFile;
use App\Metadata\Extractor;
use App\Models\Photo;

class InitDTO
{
// Import mode.
public readonly ImportMode $importMode;

// Indicates the intended owner of the image.
public readonly int $intendedOwnerId;

// Indicates whether the new photo shall be starred.
public bool $is_starred = false;

// The extracted EXIF information (populated during init phase).
public ?Extractor $exifInfo;

// The intended parent album
public ?AbstractAlbum $album = null;

// The original photo source file that is imported.
public NativeLocalFile $sourceFile;

// During initial steps if a duplicate is found, it will be placed here.
public Photo|null $duplicate = null;

// During initial steps if liveParner is found, it will be placed here.
public Photo|null $livePartner = null;

// Optional last modified data if known.
public int|null $fileLastModifiedTime = null;

public function __construct(
ImportParam $parameters,
NativeLocalFile $sourceFile,
AbstractAlbum|null $album,
int|null $fileLastModifiedTime = null
) {
$this->sourceFile = $sourceFile;
$this->importMode = $parameters->importMode;
$this->intendedOwnerId = $parameters->intendedOwnerId;
$this->is_starred = $parameters->is_starred;
$this->exifInfo = $parameters->exifInfo;
$this->album = $album;
$this->fileLastModifiedTime = $fileLastModifiedTime;
}
}

0 comments on commit fdee82c

Please sign in to comment.