diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7a59dc..18594149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ a version is cut. ### Fixed +- **Downloads and thumbnails for installations using external storage.** Two places assumed every + file sat on the server's own disk, which stopped being true the moment S3-compatible storage was + switched on. A share link to a file held in a bucket produced a broken download, and a public + listing could not draw a thumbnail for one at all — while the same file downloaded and previewed + correctly everywhere else, which made it look like the share link or the listing was at fault + rather than where the file lived. Both now read the file from wherever it actually is. Nothing + changes for installations keeping files on local disk, which is most of them. + - **One confirmation message instead of two.** Saving a new client, system user or role showed the same green "Client created." twice, stacked. So did deleting one. It was only ever cosmetic — nothing happened twice — but it read as though something had, which is the last thing a diff --git a/app/Modules/Files/Delivery/InlineFileResponse.php b/app/Modules/Files/Delivery/InlineFileResponse.php deleted file mode 100644 index acf62a47..00000000 --- a/app/Modules/Files/Delivery/InlineFileResponse.php +++ /dev/null @@ -1,55 +0,0 @@ - seeking through an hour of footage issues a long tail - * of Range requests; nginx's static handler answers those with 206s on - * its own, and drops the Content-Length below in favour of the range it - * actually served. Anything else — S3 and friends — gets a short-lived - * presigned URL carrying an inline disposition, which the object store - * ranges just as well. - * - * Callers must have established that the mime type is inline-safe first; - * PreviewKind is the allowlist, and the reason there is one. - */ -class InlineFileResponse -{ - public function make(File $file): Response|RedirectResponse - { - if ($file->disk !== 'files') { - $url = Storage::disk($file->disk)->temporaryUrl( - $file->path, - now()->addHour(), - ['ResponseContentDisposition' => ContentDisposition::inline($file->original_name)], - ); - - return redirect()->away($url); - } - - return response('', 200, [ - 'X-Accel-Redirect' => '/protected-files/'.$file->path, - 'Content-Type' => $file->mime_type, - 'Content-Disposition' => ContentDisposition::inline($file->original_name), - 'Content-Length' => (string) $file->size, - ]); - } -} diff --git a/app/Modules/Files/Delivery/StoredFileResponse.php b/app/Modules/Files/Delivery/StoredFileResponse.php new file mode 100644 index 00000000..3db34524 --- /dev/null +++ b/app/Modules/Files/Delivery/StoredFileResponse.php @@ -0,0 +1,70 @@ +disk` decides how the bytes travel. + * + * Local disk: X-Accel-Redirect, so nginx streams the file and PHP never + * touches the bytes. Anything else — S3, GCS and friends — gets a + * short-lived presigned URL carrying the disposition, which an object + * store ranges just as well. + * + * That distinction matters most for inline(): a