Attachments, Files, Thumbnails and Links
An attachment in GNU social can represent both a file or a link with a thumbnail.
Files
Storage
Not every information should be stored in the database. Large blobs of data usually
find their space in storage. The two most common file abstractions you will find in
GNU social are App\Util\TemporaryFile
and
Symfony\Component\HttpFoundation\File\UploadedFile
.
The UploadedFile
comes from Symfony and you'll find it when
working with forms that have file upload as inputs. The
TemporaryFile
is how GNU social handles/represents any file that isn't
in a permanent state, i.e., not yet ready to be moved to storage.
So, the Attachment
entity won't store the information, only point to it.
Example
Here's how the ImageEncoder
plugin creates a temporary file to manipulate an
image in a transaction fashion before committing its changes:
// TemporaryFile handles deleting the file if some error occurs
$temp = new TemporaryFile(['prefix' => 'image', 'suffix' => $extension]);
$image = Vips\Image::newFromFile($file->getRealPath(), ['access' => 'sequential']);
$width = Common::clamp($image->width, 0, Common::config('attachments', 'max_width'));
$height = Common::clamp($image->height, 0, Common::config('attachments', 'max_height'));
$image = $image->crop(0, 0, $width, $height);
$image->writeToFile($temp->getRealPath());
// Replace original file with the sanitized one
$temp->commit($file->getRealPath());
Note how we:
- created a temporary file
$temp
, - then write the in-memory
$image
manipulation of$file
to storage in$temp
- and only then commit the changes in
$temp
to$file
's location.
If anything failed in 2 we would risk corrupting the input $file
. In this case,
for performance's sake, most of the manipulation happens in memory. But it's
obvious that TemporaryFile
can also be very useful for eventual in-storage
manipulations.
Return a file via HTTP
Okay, it's fun that you can save files. But it isn't very
useful if you can't show the amazing changes or files you
generated to the client. For that, GNU social has
App\Core\GSFile
.
Example
public function avatar_view(Request $request, int $gsactor_id)
{
$res = \Component\Avatar\Avatar::getAvatarFileInfo($gsactor_id);
return \App\Core\GSFile::sendFile(filepath: $res['filepath'],
mimetype: $res['mimetype'],
output_filename: $res['title'],
disposition: 'inline');
}
Simple enough.
Attachments: Storing a reference in database
Finally, you need a way to refer to previous files.
GNU social calls that representation of Component\Attachment\Entity\Attachment
.
If a note refers to an Attachment
then you can link them
using the entity AttachmentToNote
.
Important: The core hashes the files and reuses
Attachment
s. Therefore, if you're deleting a file from storage, you must ensure it is really intended and safe.
Call the functions Attachment::validateAndStoreFileAsAttachment
and Attachment::validateAndStoreURLAsAttachment
.
Killing an attachment
Because deleting an attachment is different from deleting your
regular entity, to delete an attachment you should call the
member function kill()
. It will decrease the lives count and
only remove it if it has lost all its lives.
Thumbnails
Both files and links can have an AttachmentThumbnail
.
You can have an AttachmentThumbnail
for every Attachment
.
You can only have an AttachmentThumbnail
if you have an
attachment first.
Read a plugin such as ImageEncoder
to understand how thumbnails
can be generated from files. And StoreRemoteMedia
to understand how to generate
them from URLs.
The controller asking for them is the App\Controller\Attachment::attachment_thumbnail
with
a call to Component\Attachment\Entity\AttachmentThumbnail::getOrCreate()
.
Trade-offs between decoupling and complexity
This kind of questions are deepened in our wiki.
Despite that, in this case it is relevant enough to walk
a little through in the documentation. You'll note that
the Attachment entity has fairly specific fields such
as width
and height
. Maybe for an Attachment
you could use the width field for the cover image of a
song, or not and just leave it null. And for a song
preview you could use width for duration and leave height
as null. The point is, we could have the entities
ImageAttachment and an ImageAttachmentThumbnail being
created by the ImageEncoder plugin and move these
specificities to the plugin. But the end code would
require more database requests, become heavier,
and become harder to read. And maybe we're wasting a
bit more space (maybe!). But if that's the case, it's
far from significant. The processing cost and ease of
understanding outweighs the storage cost.
Links
We have Links entities for representing links, these are used by the Posting component to represent remote urls. These are fairly similar to the attachment entities.