126 lines
5.0 KiB
Markdown
126 lines
5.0 KiB
Markdown
# 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:
|
|
|
|
```php
|
|
// 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:
|
|
1. created a temporary file `$temp`,
|
|
2. then write the in-memory `$image` manipulation of `$file` to storage in `$temp`
|
|
3. 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
|
|
|
|
```php
|
|
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](https://agile.gnusocial.rocks/doku.php?id=attachment).
|
|
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. |