-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
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
Added creation of custom image sizes on request #10184
Added creation of custom image sizes on request #10184
Conversation
I have concerns that deleting the image file cache on each activation is 1) unnecessary and 2) heavy. I think this can be avoided by using a file path which uses a 1:1 ref to the image file. E.g. Doing this would prevent us from needing to clear the cache, as the same name would never be used to reference different sizes. I think this also prevents the need for the If we want to still delete images to prevent buildup, we can, but with the 1:1 reference, we can reduce the load down to only deleting images when a size is removed from the theme. In this case, there's no chance of edge cases when requests cross over (i.e. a deletion and creation are called at the same time). There should be significantly less "file thrashing" and I think there are other ways in which a more specific naming convention affords us more flexibility. If we are keeping the If we want to keep the existing approach, it IMO needs load testing. |
Am fine with those suggestions from product standpoint, and actually this does match cloudinary a bit closer tbh - https://cloudinary.com/documentation/image_transformations So could take some inspiration from naming
as
Skip the comma, and we have no concept of crop mode atm so skip that too. (Note renamed Could we at least clear image cache whenever Ghost upgrades though? Is that acceptable |
It's totally acceptable to me conceptually. I'm only not 100% that we know when Ghost upgrades to hang an event off of 🤔Which is weird, and explains why we struggle with update notifications 🤔 🤔 🤔 🤔 I can think of several future things we'd want that for... At the very very least, we know when we do a database migration, and can definitely clear the image cache on those right now. |
P.S. I think cloudinary URLs are perfect ref material here, but doesn't
become
or no slash
If both w & h are specified? Also, what happened to fit & fill crop modes from the spec?! |
yeah that one
CSS happened .image-class {
object-fit: fill;
object-fit: contain;
object-fit: cover;
object-fit: none;
object-fit: scale-down;
} Can certainly add more image cropping later if we want but actually not that essential for the sake of shipping a first version |
👍 |
Forgot to add, I'm not sure about this suggestion. Egg ran naming past me for this, and the way I understood it was to mean that the regular To me |
Updated this to no longer do any "garbage collection" of the resized images and to work correctly with the dimension urls |
const storage = require('../../../../adapters/storage'); | ||
const activeTheme = require('../../../../services/themes/active'); | ||
|
||
const SIZE_PATH_REGEX = /^\/size\/([^/]+)\//; |
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
|
||
// CASE: unknown size or missing size config | ||
const imageDimensionConfig = imageDimensions[requestedDimension]; | ||
if (!imageDimensionConfig || (!imageDimensionConfig.width && !imageDimensionConfig.height)) { |
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change that needs addressing is #10184 (comment), the rest is more of an optional styling improvement. Would also be nice to get some unit test in 👍
@gargol I've updated this PR - address the main issue, and added some test - they're a start. |
@@ -0,0 +1,66 @@ | |||
const path = require('path'); | |||
const sharp = require('sharp'); |
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
Need to raise a followup issue for this to reinstate deletion on update as a dependency on #10236. |
This reverts commit 9204dfcc73a6a79d597dbf23651817bcbfc59991.
This reverts commit b45fdb405a05faeaf4bd87e977c4ac64ff96b057.
This reverts commit a587cd6bae45b68a293b2d5cfd9b7705a29e7bfa.
Co-Authored-By: allouis <[email protected]>
require('sharp'); | ||
return resizeImage(buffer, options); | ||
} catch (e) { | ||
return Promise.resolve(buffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI design hint: libraries should return an error, callers need to decide what to do if an error is thrown/returned. Callers can ignore errors, callers can return errors to the next chain. Whatever...
Furthermore: If sharp isn't installed or resizeImage throws an error, we would save the same buffer stream as the resized image? Is that wanted? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lib returns a promise, which will reject if an error occurs, so the consumer of this is totally capable of catching any errors.
resizeImage returns promise, so errors from there will be handled by the library consumer - if sharp doesn't exist then yeah - we will save the resizedImage - i think this is a reasonable fallback for the functionality. @ErisDS what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lib returns a promise, which will reject if an error occurs, so the consumer of this is totally capable of catching any errors.
IMO a library should always return a specific/custom/handled error. Otherwise, multiple/different callers need to handle an error from a third party library or from node. Plus the callers would need to wrap the error in case they want to forward the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah - I think a specific error is good, I'm not sure how specific we can be on top of sharps errors though.
I noticed in this file we use InternalServer errors but that doesn't seem correct as the library has nothing to do with servers it just deals with image buffers and the filesystem 🦐
Do you think we should create some image manipulation errors to use in the module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed in this file we use InternalServer errors but that doesn't seem correct as the library has nothing to do with servers it just deals with image buffers and the filesystem 🦐
There are many definitions of what an internat server error is.
Do you think we should create some image manipulation errors to use in the module?
In general: We did not create custom error types for custom situations in the past, because
- an error is mostly http driven
- the most common http codes are reflected in our error set
- we can granulary define an error using "message", "type", "statusCode"
There is no need to block this PR, but it's important that all of us have a common thinking of how libraries are designed, how error handling works etc :)
resizeImage returns promise, so errors from there will be handled by the library consumer - if sharp doesn't exist then yeah - we will save the resizedImage - i think this is a reasonable fallback for the functionality.
This just needs answering 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merging as is - will do a bit of clean up later
module.exports.process = process; | ||
module.exports.safeResizeImage = (buffer, options) => { | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like this separation of a check logic for sharp being installed, maybe we should unify this handling for both safeResizeImage
& process
to keep it DRY? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that idea, but I think can be done in a separate PR so that we can refactor them to share some sharp code too
refs #10181 * Added initial handleImageSizes middleware * Implemented saveRaw method on local file storage * Wired up handleImageSizes middleware * Implemented delete for LocalFileStorage * Removed delete method from theme Storage class * Deleted sizes directory when theme is activated * Ensured that smaller images are not enlarged * Renamed sizes -> size * Exited middleware as early as possible * Called getStorage as late as possible * Updated image sizes middleware to handle dimension paths * Revert "Deleted sizes directory when theme is activated" This reverts commit 9204dfcc73a6a79d597dbf23651817bcbfc59991. * Revert "Removed delete method from theme Storage class" This reverts commit b45fdb405a05faeaf4bd87e977c4ac64ff96b057. * Revert "Implemented delete for LocalFileStorage" This reverts commit a587cd6bae45b68a293b2d5cfd9b7705a29e7bfa. * Fixed typo Co-Authored-By: allouis <[email protected]> * Redirected to original image if no image_sizes config * Refactored redirection because rule of three * Updated comments * Added rubbish tests * Added @todo comment for handleImageSizes tests * Added safeResizeImage method to image manipulator * Used image manipulator lib in image_size middleware
refs #10181
This implements the custom image sizes to work with #10182
Changes
saveRaw
method on theLocalFileStorage
This updates the LocalFileStorage to have the ability to take a buffer and a full path, and store it, overwriting anything that was already there. This is a destructive method and shouldn't be used lightly.
We need this method to ensure that we can write resized images exactly where we want them to be, not based on the current month or year. Being able to write a buffer means we don't have to save a temp file and then delete it.
delete
method on theLocalFileStorage
This adds thedelete
method used in theThemeStorage
class to theLocalFileStorage
- it's need to remove thesizes
directory when the theme is changed. I've also removed the method fromThemeStorage
as it inherits fromLocalFileStorage
handleImageSize
middlewareThis middleware is added to the to the
/content/<images!!>
path and will detect an image beginning with/size/<requestedSize>
- after which 1 of 3 things will happen/size/<requestedSize>
prefix.If the prefix is not detected this middleware will fall through.
Theme serviceactivate
method deletessizes
directoryIt also adds some code the the theme service, to delete thesizes
directory whenever a theme is activated.This i'm not 100% on - I feel like this should be handled in n event listen for the'services.themes.activated'
event, but couldn't decide on where that listener should be created. As thesizes
directory is theme specific (it relies on config in the themes package.json) this didn't seem like an inappropriate place to put it - just not the best.