s3 bandwidth usage fix

This commit is contained in:
Čarodej
2022-03-29 10:06:20 +02:00
parent 8c59501907
commit 9d4d1ba9aa
28 changed files with 205 additions and 214 deletions

View File

@@ -33,7 +33,7 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule): void
{
if (! is_storage_driver('local')) {
if (! isStorageDriver('local')) {
$schedule->call(
fn () => resolve(DeleteFailedFilesAction::class)()
)->everySixHours();

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Users\Controllers;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class GetAvatarController
{
@@ -12,10 +12,10 @@ class GetAvatarController
*/
public function __invoke(
string $basename
): StreamedResponse | FileNotFoundException {
): StreamedResponse | Response {
// Check if file exist
if (! Storage::exists("/avatars/$basename")) {
abort(404);
return response('File not found', 404);
}
// Return avatar

View File

@@ -42,7 +42,7 @@ class UserSetting extends Model
$link = [];
// Get avatar from external storage
if ($this->attributes['avatar'] && ! is_storage_driver('local')) {
if ($this->attributes['avatar'] && ! isStorageDriver('local')) {
foreach (config('vuefilemanager.avatar_sizes') as $item) {
$filePath = "avatars/{$item['name']}-{$this->attributes['avatar']}";

View File

@@ -2,36 +2,44 @@
namespace Domain\Files\Actions;
use Domain\Files\Models\File;
use Illuminate\Http\Response;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class DownloadFileAction
{
/**
* Call and download file
*/
public function __invoke(
File $file,
string $user_id,
): BinaryFileResponse {
public function __invoke(File $file): Response|StreamedResponse|RedirectResponse
{
// Get file path
$path = "files/$user_id/$file->basename";
// Check if file exist
if (! Storage::exists($path)) {
abort(404);
}
$filePath = "files/$file->user_id/$file->basename";
// Get pretty name
$pretty_name = get_pretty_name($file->basename, $file->name, $file->mimetype);
$fileName = getPrettyName($file->basename, $file->name, $file->mimetype);
return response()
->download(Storage::path($path), $pretty_name, [
'Accept-Ranges' => 'bytes',
'Content-Type' => Storage::mimeType($path),
'Content-Length' => Storage::size($path),
'Content-Range' => 'bytes 0-600/' . Storage::size($path),
'Content-Disposition' => "attachment; filename=$pretty_name",
]);
// Check if file exist
if (! Storage::exists($filePath)) {
return response('The file not found.', 404);
}
// Format response header
$header = [
'ResponseAcceptRanges' => 'bytes',
'ResponseContentType' => Storage::mimeType($filePath),
'ResponseContentLength' => Storage::size($filePath),
'ResponseContentRange' => 'bytes 0-600/' . Storage::size($filePath),
'ResponseContentDisposition' => "attachment; filename=$fileName",
];
// If s3 redirect to temporary download url
if (isStorageDriver('s3')) {
return redirect()->away(Storage::temporaryUrl($filePath, now()->addHour(), $header));
}
// Download file
return Storage::download($filePath, $fileName, $header);
}
}

View File

@@ -2,6 +2,7 @@
namespace Domain\Files\Actions;
use Domain\Files\Models\File;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -13,18 +14,18 @@ class DownloadThumbnailAction
public function __invoke(
string $filename,
File $file
): StreamedResponse {
): StreamedResponse|Response {
// Get file path
$path = "/files/$file->user_id/$filename";
$filePath = "/files/$file->user_id/$filename";
// Check if file exist
if (! Storage::exists($path)) {
if (! Storage::exists($filePath)) {
// Get original file path
$substituteFilePath = "/files/$file->user_id/$file->basename";
// Check if original file exist
if (! Storage::exists($substituteFilePath)) {
abort(404);
return response('File not found', 404);
}
// Return image thumbnail
@@ -32,6 +33,6 @@ class DownloadThumbnailAction
}
// Return image thumbnail
return Storage::download($path, $filename);
return Storage::download($filePath, $filename);
}
}

View File

@@ -82,7 +82,7 @@ class UploadFileAction
($this->createImageThumbnail)($fileName, $file, $user->id);
// Move files to external storage
if (! is_storage_driver('local')) {
if (! isStorageDriver('local')) {
($this->moveFileToExternalStorage)($fileName, $user->id);
}

View File

@@ -2,12 +2,13 @@
namespace Domain\Files\Controllers\FileAccess;
use Gate;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use App\Http\Controllers\Controller;
use Domain\Files\Models\File as UserFile;
use Domain\Files\Models\File;
use Domain\Files\Actions\DownloadFileAction;
use Domain\Traffic\Actions\RecordDownloadAction;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class GetFileController extends Controller
{
@@ -19,31 +20,30 @@ class GetFileController extends Controller
public function __invoke(
string $filename,
): Response|BinaryFileResponse {
$file = UserFile::withTrashed()
): Response|RedirectResponse|StreamedResponse {
// Get requested file
$file = File::withTrashed()
->where('basename', $filename)
->firstOrFail();
// Check if user can download file
if (! $file->owner->canDownload()) {
return response([
'type' => 'error',
'message' => 'This user action is not allowed.',
], 401);
return response(userActionNotAllowedError(), 401);
}
// Check if user has privileges to download file
if (! Gate::any(['can-edit', 'can-view'], [$file, null])) {
abort(403, 'Access Denied');
return response(accessDeniedError(), 403);
}
// TODO: resolve video buffering
// Store user download size
($this->recordDownload)(
file_size: (int) $file->getRawOriginal('filesize'),
file_size: $file->filesize,
user_id: $file->user_id,
);
return ($this->downloadFile)($file, $file->user_id);
return ($this->downloadFile)($file);
}
}

View File

@@ -6,6 +6,7 @@ use Illuminate\Http\Request;
use Domain\Files\Models\File;
use App\Http\Controllers\Controller;
use Domain\Files\Actions\DownloadThumbnailAction;
use Illuminate\Http\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
@@ -13,19 +14,21 @@ class GetThumbnailController extends Controller
{
public function __construct(
private DownloadThumbnailAction $downloadThumbnail,
) {
}
) {}
public function __invoke(
Request $request,
string $filename,
): FileNotFoundException | StreamedResponse {
): FileNotFoundException | StreamedResponse | Response {
// Get requested thumbnail
$file = File::withTrashed()
->where('basename', substr($filename, 3))
->firstOrFail();
// Check if user has privileges to download file
if (! Gate::any(['can-edit', 'can-view'], [$file, null])) {
abort(403, 'Access Denied');
return response(accessDeniedError(), 403);
}
return ($this->downloadThumbnail)($filename, $file);

View File

@@ -2,14 +2,15 @@
namespace Domain\Files\Controllers\FileAccess;
use Domain\Files\Models\File;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Domain\Sharing\Models\Share;
use App\Http\Controllers\Controller;
use Domain\Files\Actions\DownloadFileAction;
use Domain\Traffic\Actions\RecordDownloadAction;
use Domain\Sharing\Actions\ProtectShareRecordAction;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Domain\Sharing\Actions\VerifyAccessToItemWithinAction;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* Get file public
@@ -27,13 +28,10 @@ class VisitorGetFileController extends Controller
public function __invoke(
$filename,
Share $shared,
): BinaryFileResponse|Response {
): StreamedResponse|RedirectResponse|Response {
// Check if user can download file
if (! $shared->user->canDownload()) {
return response([
'type' => 'error',
'message' => 'This user action is not allowed.',
], 401);
return response(userActionNotAllowedError(), 401);
}
// Check ability to access protected share files
@@ -49,11 +47,11 @@ class VisitorGetFileController extends Controller
// Store user download size
($this->recordDownload)(
file_size: (int) $file->getRawOriginal('filesize'),
file_size: $file->filesize,
user_id: $shared->user_id,
);
// Finally, download file
return ($this->downloadFile)($file, $shared->user_id);
return ($this->downloadFile)($file);
}
}

View File

@@ -40,7 +40,7 @@ class VisitorGetThumbnailController extends Controller
// Store user download size
($this->recordDownload)(
file_size: (int) $file->getRawOriginal('filesize'),
file_size: $file->filesize,
user_id: $shared->user_id,
);

View File

@@ -25,7 +25,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
* @property string user_id
* @property string parent_id
* @property string thumbnail
* @property string filesize
* @property int filesize
* @property string type
* @property string basename
* @property string name
@@ -100,7 +100,7 @@ class File extends Model
->all();
// Generate thumbnail link for external storage service
if ($this->type === 'image' && ! is_storage_driver('local')) {
if ($this->type === 'image' && ! isStorageDriver('local')) {
foreach ($thumbnail_sizes as $item) {
$filePath = "files/{$this->user_id}/{$item['name']}-{$this->basename}";
@@ -139,23 +139,6 @@ class File extends Model
*/
public function getFileUrlAttribute(): string
{
// Get file from external storage
if (! is_storage_driver('local')) {
$file_pretty_name = is_storage_driver('backblaze')
? Str::snake(mb_strtolower($this->attributes['name']))
: get_pretty_name($this->attributes['basename'], $this->attributes['name'], $this->attributes['mimetype']);
$header = [
'ResponseAcceptRanges' => 'bytes',
'ResponseContentType' => $this->attributes['mimetype'],
'ResponseContentLength' => $this->attributes['filesize'],
'ResponseContentRange' => 'bytes 0-600/' . $this->attributes['filesize'],
'ResponseContentDisposition' => 'attachment; filename=' . $file_pretty_name,
];
return Storage::temporaryUrl("files/$this->user_id/{$this->attributes['basename']}", now()->addDay(), $header);
}
// Get thumbnail from local storage
$route = route('file', ['name' => $this->attributes['basename']]);
@@ -166,7 +149,6 @@ class File extends Model
// Set upload request public url
if ($this->uploadRequestAccess) {
// TODO: review request for s3
$route .= "/upload-request/$this->uploadRequestAccess";
}

View File

@@ -1,9 +1,9 @@
<?php
namespace Domain\Settings\Controllers;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class GetAppImageController
{
@@ -12,10 +12,10 @@ class GetAppImageController
*/
public function __invoke(
string $basename
): StreamedResponse | FileNotFoundException {
): StreamedResponse | Response {
// Check if file exist
if (! Storage::exists("/system/$basename")) {
abort(404);
return response('File not found', 404);
}
// Return avatar

View File

@@ -3,6 +3,7 @@
namespace Domain\Sharing\Controllers;
use App\Http\Controllers\Controller;
use Domain\Files\Actions\DownloadFileAction;
use Domain\Files\Models\File;
use Domain\Sharing\Actions\ProtectShareRecordAction;
use Domain\Sharing\Actions\VerifyAccessToItemWithinAction;
@@ -16,10 +17,11 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
class DirectlyDownloadFileController extends Controller
{
public function __construct(
private DownloadFileAction $downloadFile,
private RecordDownloadAction $recordDownload,
private ProtectShareRecordAction $protectShareRecord,
private VerifyAccessToItemWithinAction $verifyAccessToItemWithin,
){}
) {}
public function __invoke(
Share $share
@@ -51,32 +53,10 @@ class DirectlyDownloadFileController extends Controller
// Store user download size
($this->recordDownload)(
file_size: (int)$file->getRawOriginal('filesize'),
file_size: $file->filesize,
user_id: $share->user_id,
);
// Get file path
$path = "files/$share->user_id/$file->basename";
// Check if file exist
if (!Storage::exists($path)) {
abort(404);
}
// Get pretty name
$pretty_name = get_pretty_name($file->basename, $file->name, $file->mimetype);
// If s3 redirect to temporary url
if (is_storage_driver('s3')) {
return redirect()->away(Storage::temporaryUrl($path, now()->addHour(), [
'ResponseAcceptRanges' => 'bytes',
'ResponseContentType' => Storage::mimeType($path),
'ResponseContentLength' => Storage::size($path),
'ResponseContentRange' => 'bytes 0-600/' . Storage::size($path),
'ResponseContentDisposition' => "attachment; filename=$pretty_name",
]));
}
return Storage::download($path, $pretty_name);
return ($this->downloadFile)($file);
}
}

View File

@@ -2,13 +2,12 @@
namespace Domain\UploadRequest\Controllers\FileAccess;
use Domain\Files\Models\File;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Domain\Files\Actions\DownloadFileAction;
use Domain\UploadRequest\Models\UploadRequest;
use Domain\Traffic\Actions\RecordDownloadAction;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Routing\ResponseFactory;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* Get shared file record
@@ -24,7 +23,7 @@ class GetFileFromUploadRequestController
public function __invoke(
string $filename,
UploadRequest $uploadRequest
): Application|ResponseFactory|Response|BinaryFileResponse {
): StreamedResponse|RedirectResponse|Response {
// Get file
$file = File::where('user_id', $uploadRequest->user_id)
->where('basename', $filename)
@@ -32,11 +31,11 @@ class GetFileFromUploadRequestController
// Store user download size
($this->recordDownload)(
file_size: (int) $file->getRawOriginal('filesize'),
file_size: $file->filesize,
user_id: $uploadRequest->user_id,
);
// Finally, download file
return ($this->downloadFile)($file, $uploadRequest->user_id);
return ($this->downloadFile)($file);
}
}

View File

@@ -33,7 +33,7 @@ class GetThumbnailFromUploadRequestController extends Controller
// Store user download size
($this->recordDownload)(
file_size: (int) $file->getRawOriginal('filesize'),
file_size: $file->filesize,
user_id: $uploadRequest->user_id,
);

View File

@@ -38,12 +38,12 @@ class ZipAction
// Add file into zip
if (Storage::exists($filePath)) {
// local disk
if (is_storage_driver('local')) {
if (isStorageDriver('local')) {
$zip->add(Storage::path($filePath), $file->name);
}
// s3 client
if (is_storage_driver('s3')) {
if (isStorageDriver('s3')) {
$bucketName = config('filesystems.disks.s3.bucket');
$zip->add("s3://$bucketName/$filePath", $file->name);
@@ -75,12 +75,12 @@ class ZipAction
$zipDestination = "{$file['folder_path']}/{$file['name']}";
// local disk
if (is_storage_driver('local')) {
if (isStorageDriver('local')) {
$zip->add(Storage::path($filePath), $zipDestination);
}
// s3 client
if (is_storage_driver('s3')) {
if (isStorageDriver('s3')) {
$bucketName = config('filesystems.disks.s3.bucket');
$zip->add("s3://$bucketName/$filePath", $zipDestination);

15
src/Support/errors.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
function accessDeniedError(): array {
return [
'type' => 'error',
'message' => 'Access Denied',
];
}
function userActionNotAllowedError(): array {
return [
'type' => 'error',
'message' => 'This user action is not allowed.',
];
}

View File

@@ -269,13 +269,13 @@ if (! function_exists('get_storage')) {
}
}
if (! function_exists('is_storage_driver')) {
if (! function_exists('isStorageDriver')) {
/**
* Check if is running AWS s3 as storage
*
* @return bool
*/
function is_storage_driver($driver)
function isStorageDriver($driver)
{
if (is_array($driver)) {
return in_array(config('filesystems.default'), $driver);
@@ -722,7 +722,7 @@ if (! function_exists('get_file_type_from_mimetype')) {
}
}
if (! function_exists('get_pretty_name')) {
if (! function_exists('getPrettyName')) {
/**
* Format pretty name file
*
@@ -731,11 +731,11 @@ if (! function_exists('get_pretty_name')) {
* @param $mimetype
* @return string
*/
function get_pretty_name($basename, $name, $mimetype)
function getPrettyName($basename, $name, $mimetype): string
{
$file_extension = substr(strrchr($basename, '.'), 1);
if (strpos($name, $file_extension) !== false) {
if (str_contains($name, $file_extension)) {
return $name;
}