Uploads carry the name the file came with (#83)
An upload sends its file name next to the raw bytes, and the server keeps it in a sidecar next to the content-addressed file. Both pickers caption every tile and row with it, and an asset made there is named after the file instead of "Texture" / "Font". Last upload of the same content wins. The API's own set_error_handler ignores error_reporting(), so @-silenced warnings still throw: a missing sidecar took the whole listing down with a 500. Existence is checked instead of suppressed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
86b5363a01
commit
99c5d2ad78
21 files changed
+347
-61
No files matched your search
@@ -57,10 +57,14 @@ final class TextureStore
|
||||
* The extension is derived from the sniffed content, never from anything the client
|
||||
* says, so no request can choose the name or the type of the file it creates.
|
||||
*
|
||||
* @return array{file:string,width:int,height:int,bytes:int}
|
||||
* `$originalName` is kept beside the file as a caption (see OriginalName) – the picture may be
|
||||
* re-encoded on the way, so it can name a `.png` that is stored as `.webp`. That is the point:
|
||||
* it records where the file came from, not what it now is.
|
||||
*
|
||||
* @return array{file:string,name:string,width:int,height:int,bytes:int}
|
||||
* @throws InvalidImageException when the bytes are not a supported image
|
||||
*/
|
||||
public function store(string $bytes): array
|
||||
public function store(string $bytes, string $originalName = ''): array
|
||||
{
|
||||
$size = @getimagesizefromstring($bytes);
|
||||
if ($size === false) {
|
||||
@@ -88,8 +92,15 @@ final class TextureStore
|
||||
if (!is_file($path)) {
|
||||
$this->writeFile($path, $bytes);
|
||||
}
|
||||
OriginalName::write($this->dir, $file, $originalName);
|
||||
|
||||
return ['file' => $file, 'width' => $width, 'height' => $height, 'bytes' => strlen($bytes)];
|
||||
return [
|
||||
'file' => $file,
|
||||
'name' => OriginalName::read($this->dir, $file),
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'bytes' => strlen($bytes),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,19 +110,22 @@ final class TextureStore
|
||||
* records the size, so a picker that only knew the file name could not create one without
|
||||
* decoding the picture first. An unreadable file reports 0 × 0 and the picker skips it.
|
||||
*
|
||||
* @return list<array{file:string,width:int,height:int,bytes:int,updatedAt:string}>
|
||||
* @return list<array{file:string,name:string,width:int,height:int,bytes:int,updatedAt:string}>
|
||||
*/
|
||||
public function list(): array
|
||||
{
|
||||
$items = [];
|
||||
foreach (glob($this->dir . '/*.*') ?: [] as $path) {
|
||||
$file = basename($path);
|
||||
// Sidecars live in the same directory and are skipped here: `{hash}.png.json` is not
|
||||
// a valid stored name.
|
||||
if (!self::isValidName($file)) {
|
||||
continue;
|
||||
}
|
||||
$size = @getimagesize($path);
|
||||
$items[] = [
|
||||
'file' => $file,
|
||||
'name' => OriginalName::read($this->dir, $file),
|
||||
'width' => (int) ($size[0] ?? 0),
|
||||
'height' => (int) ($size[1] ?? 0),
|
||||
'bytes' => (int) filesize($path),
|
||||
@@ -128,7 +142,11 @@ final class TextureStore
|
||||
return false;
|
||||
}
|
||||
$path = $this->dir . '/' . $file;
|
||||
return is_file($path) && unlink($path);
|
||||
if (!is_file($path) || !unlink($path)) {
|
||||
return false;
|
||||
}
|
||||
OriginalName::delete($this->dir, $file);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user