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

Refactor Model Architecture #1055

Merged
merged 319 commits into from
Jan 13, 2022
Merged

Refactor Model Architecture #1055

merged 319 commits into from
Jan 13, 2022

Conversation

nagmat84
Copy link
Collaborator

@nagmat84 nagmat84 commented Jul 10, 2021

Updated Summary Nov 28th 2021

This is the merger of the previous PR "Refactor photo model" and PR #1101 Refactor album model. See PR #1101 for what has changed there.

Old Summary (of "Refactor photo model" only)

As previously discussed on Gitter this PR intends to rectify two aspects:

  • The way how the different size variants of a photo are managed has been cleaned up. The size variants now live in their own table size_variants and are independent of each other, i.e. each has its own file path, its own size attributes and so on. The previously awkward mutual dependencies are gone.
  • The hard-coded path and magic constants all throughout the code are gone. I.e. there should not be any code left which relies on the original size of an image being stored in a folder named big or that certain files have @2 in their filename.

Unfortunately, the amount of things which needed to be reworked became much larger than expected. In detail the following aspected have been changed:

  • The way how files are named has been moved to a contract SizeVariantNamingStrategy. At the moment the only implementing class is SizeVariantLegacyNamingStrategy which mimics the old behaviour.
  • The creation of the size variants has been moved to a contract SizeVariantFactory. At the moment the only implementing class is SizeVariantDefaultFactory which mimics the old behaviour.
  • Removal of a lot of code duplication. The code which creates size variants only exists in SizeVariantDefaultFactory. Previously, the same code (with minor derivations) existed several times (for uploading/creating new photos for re-creating missing size variants by the console command, etc.)
  • Complete rework of the strategy classes for photo upload. Previously, there had been two strategies StrategyPhoto and StrategyDuplicate. The code to handle the upload of live photos had been scattered in between. As this part needed to be completely reworked anyway (due to the changes how the size variants are created), there are now four strategies which cleanly encapsulate the upload of live photos, too.
  • The way how photos (and size variants) are deleted has been changed. Previously, it was the responsibility of the caller to first call predelete on these entities in order to properly clean-up the actual files followed by delete. Now it is save to directly call delete and the classes will take care of proper deletion.
  • The way how entities with time-based IDs are persisted has been changed. Previously, it was the responsibility of the caller to use a special save method instead of myEntity->save when an entity had to be persisted for the first time. The former created a time-based ID and took care of possible duplicates, the latter would have created incrementing IDs as it is the default for Laravel. The ID handling has been moved to a trait HasTimeBasedID which is used by entities with time-based IDs. Now, it is save to call the usual myEntity->save.
  • Probably a lot more than I do not re-call

@nagmat84 nagmat84 marked this pull request as draft July 10, 2021 15:34
@nagmat84 nagmat84 requested review from ildyria, kamil4 and d7415 July 10, 2021 15:35
Copy link
Contributor

@kamil4 kamil4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm taking a pause at 25% (26/104 files viewed) so that I can give you the feedback I collected so far.

app/Actions/Album/Create.php Outdated Show resolved Hide resolved
app/Actions/Album/CreateTag.php Outdated Show resolved Hide resolved
app/Actions/Album/Photos.php Show resolved Hide resolved
app/Actions/Photo/Archive.php Outdated Show resolved Hide resolved
app/Actions/Photo/Archive.php Show resolved Hide resolved
app/Actions/Photo/Create.php Outdated Show resolved Hide resolved
app/Actions/Photo/Create.php Outdated Show resolved Hide resolved
app/Actions/Photo/Duplicate.php Outdated Show resolved Hide resolved
app/Actions/Photo/Extensions/ArchiveFileInfo.php Outdated Show resolved Hide resolved
Copy link
Contributor

@kamil4 kamil4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'm up to 40/104 files viewed; I need to pause here as it's 11:30pm already...

app/Actions/Photo/Setter.php Show resolved Hide resolved
app/Actions/Photo/Strategies/AddStandaloneStrategy.php Outdated Show resolved Hide resolved
app/Actions/Photo/Strategies/AddPhotoPartnerStrategy.php Outdated Show resolved Hide resolved
app/Actions/Photo/Strategies/RotateStrategy.php Outdated Show resolved Hide resolved
app/Actions/Photo/Strategies/RotateStrategy.php Outdated Show resolved Hide resolved
app/Actions/Photo/Strategies/RotateStrategy.php Outdated Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Jul 17, 2021

Codecov Report

Merging #1055 (ed17b7e) into master (a7de020) will decrease coverage by 0.25%.
The diff coverage is 67.50%.

Copy link
Contributor

@kamil4 kamil4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping to finish today but I only got to 52 /105 files viewed and it's past 11pm. Weekend coding sucks when one has young kids at home... 😞

app/Console/Commands/Ghostbuster.php Outdated Show resolved Hide resolved
app/Console/Commands/Ghostbuster.php Outdated Show resolved Hide resolved
app/Actions/Photo/Duplicate.php Outdated Show resolved Hide resolved
app/Console/Commands/Ghostbuster.php Outdated Show resolved Hide resolved
app/Console/Commands/Ghostbuster.php Outdated Show resolved Hide resolved
app/Console/Commands/Ghostbuster.php Outdated Show resolved Hide resolved
app/Console/Commands/VideoData.php Outdated Show resolved Hide resolved
Copy link
Contributor

@kamil4 kamil4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there (85 / 111 files viewed). I didn't want to push it as it's getting late and I need a fresh mind to look over app/Models/Photo.php and such...

app/Http/Controllers/PhotoController.php Outdated Show resolved Hide resolved
$this->extractReferenceFromRaw($original->full_path, $original->width, $original->height);
} elseif ($this->photo->isVideo()) {
if (empty($this->photo->aperture)) {
Logs::error(__METHOD__, __LINE__, 'Media file is reported to be a video, but aperture (aka duration) has not been extracted');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been wondering for a while if we aren't being overly restrictive about this. If the duration is unknown, wouldn't it be better to simply extract the first frame (position 0) rather than bailing out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could do that. Shall we? I'm open for everything.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, if extractReferenceFromVideo(string $fullPath, float $framePosition) is called with a frame position from the middle of the video and fails, then it tries to extract a frame at position 0 as a recovery strategy.

$video = $ffmpeg->open($fullPath);
try {
  $this->extractFrame($video, $framePosition);
} catch (\RuntimeException $e) {
  Logs::notice(__METHOD__, __LINE__, 'Fallback: Try to extract snapshot at position 0');
  $this->extractFrame($video, 0);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong opinion in my case.

app/Image/SizeVariantDefaultFactory.php Show resolved Hide resolved
throw new \RuntimeException($errMsg);
}
if (Configs::get_value('lossless_optimization')) {
ImageOptimizer::optimize($this->referenceFullPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming you inherited this code but it strikes me as completely unnecessary to optimize a temporary image (it's getting deleted as soon as the thumbs and small variants get extracted). If anywhere, I would expect it in the raw handler, not in videos, Or am I missing something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I inherited the code and I had the very same thought. Moreover I have wondered, if the optimization does anything good or if the optimization has not actually the inverse effect. Normally, "optimization" always means to throw away information (e.g. smoothing things out). But we use that temporary image as an interim image for further scaling and cropping. So, maybe we should not optimize the image here, but only the final (scaled) result.

Anyway, I am not the original author of that code. So maybe @ildyria can shed a light on this. He is the author of the original code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#659 :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not optimize the image here, but only the final (scaled) result

The image optimization is lossless and requires additional software.
I do agree that only the final scaled result should be optimized.

app/Image/SizeVariantDefaultFactory.php Outdated Show resolved Hide resolved
app/Image/SizeVariantDefaultFactory.php Show resolved Hide resolved
* IMHO, there is an amazing number of places which somehow deal with
* "mime type-ish" sort of values with subtle differences.
*
* TODO: Thoroughly refactor this.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is another PR. I fear it will never end. 🥺

public function save(array $options = []): bool
{
$result = false;
$retryCounter = 5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limit is a new thing, isn't it? On the one hand, I agree that looping forever was a poor style. On the other hand, is 5 large enough? Keep in mind that for users with 32-bit IDs, the IDs have a full-second resolution, so they are very likely to conflict when uploading multiple photos at a time...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the limit is a new thing to avoid infinite loops. I expect five to be sufficiently large. The IDs are time-based, i.e. strictly monotonic and not random (so the birthday paradox does not apply).

Moreover, PHP is single-threaded within the same request. Hence, if a user uploads or imports a set of photos within a single request then we have no problem even if the user as a high-speed connection and several photos are imported during the same second. If PHP sleeps for one second, then the whole sequence of photos is delayed. (In this case, the limit 1 should actually be enough.)

We only run into problems, if multiple users (or the same user) uploads photos parallel requests. Honestly, I have no gut feeling which would be an appropriate limit in this case.

Copy link
Member

@ildyria ildyria Aug 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover, PHP is single-threaded within the same request. Hence, if a user uploads or imports a set of photos within a single request then we have no problem even if the user as a high-speed connection and several photos are imported during the same second. If PHP sleeps for one second, then the whole sequence of photos is delayed. (In this case, the limit 1 should actually be enough.)

Actually no, the JS front-end throws multiple requests at the same time to the server for a single user, that is why you are sometimes processing 4 images in parallel (and it is a good thing as long as you don't hit a memory limit).

Though I do expect the limit of 5 to be sufficiently large. :)

app/Models/Extensions/SizeVariants.php Outdated Show resolved Hide resolved
app/Models/Extensions/SizeVariants.php Outdated Show resolved Hide resolved
Copy link
Contributor

@kamil4 kamil4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done reviewing (finally)!

So where is the corresponding front end PR? I see the compiled changes here, and overall I must say that they seem surprisingly minor. I guess much of the work was done with the previous PR that introduced size variants to the front end? Unless that part is still WIP?

* ensures that the string is stored correctly formatted at the DB right
* from the beginning and then simply return the stored string instead of
* re-format the string on every fetch.
* TODO: Refactor this.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, there is an argument to be made that there is no such thing as "stored correctly formatted" because it will depend on localization. But the current code ignores this issue as well, of course...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's inherited code. (I just improved PHPdoc comment.) Your are probably right about localization, but that's another topic. When I inherited the code, I just wondered why we do the conversion from a (arbitrary) rational fraction to a unit fraction each time when the data is fetched from the DB. Why don't we do it once when we store the data and then just serve it directly from the database on fetch?

Copy link
Member

@ildyria ildyria Aug 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it would be nicer to optimize this conversion upon original upload instead of every time the data is fetched.

But for now I would leave it as a todo. :)

app/Models/Photo.php Show resolved Hide resolved
app/Models/Photo.php Outdated Show resolved Hide resolved
app/Models/SizeVariant.php Outdated Show resolved Hide resolved
app/Models/SizeVariant.php Outdated Show resolved Hide resolved
database/migrations/2018_08_15_103716_move_photos.php Outdated Show resolved Hide resolved
@nagmat84 nagmat84 marked this pull request as ready for review July 31, 2021 14:30
Copy link
Member

@ildyria ildyria left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@nagmat84 please check if you are happy with the view very minor changes I made.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Aug 7, 2021

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 50 Code Smells

No Coverage information No Coverage information
0.8% 0.8% Duplication

@nagmat84 nagmat84 force-pushed the refactor_photo_model branch from 70f4391 to 4459a25 Compare January 7, 2022 14:57
@nagmat84 nagmat84 force-pushed the refactor_photo_model branch from dc9c1fd to ed17b7e Compare January 13, 2022 17:45
@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 64 Code Smells

No Coverage information No Coverage information
0.8% 0.8% Duplication

@nagmat84 nagmat84 merged commit 32c374d into master Jan 13, 2022
@nagmat84 nagmat84 deleted the refactor_photo_model branch January 13, 2022 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants