1stevengrant's avatar

Download a file from s3

I'm building a small asset management system within Laravel.

So far, so good but I'm having problems making my file available for download.

This method handles the upload

public function store(AssetRequest $request)
    {

        // Initialise new asset and set the name
        // from the form
        $asset = new Asset(array(
            'name' => $request->get('name')
        ));

        $asset->user_id = Auth::user()->id;
        
        // save the asset to the db
        $asset->save();
        // set the file var = form input
        $file = $request->file('asset_path');
        $extension = $file->getClientOriginalExtension();
        // modify the asset name
        $assetFile = $asset->id . '.' . $request->file('asset_path')->getClientOriginalExtension();
        // push the new asset to s3
        Storage::disk('s3')->put('uploads/' . $assetFile, file_get_contents($file));
        $asset->mime = $file->getClientMimeType();
        $asset->original_filename = $file->getClientOriginalName();
        $asset->filename = $assetFile;
        $asset->file_extension = $extension;
        // return ok
        $asset->save();
        return \Redirect::route('asset.create')->with('message', 'Asset added!');
    }

and it works swell (can't help but feel it could be simpler).

For my view I have

<a href="/download/{{ $asset->id}}>download asset</a>

and the method

public function downloadAsset($id)
    {
        $fs = Storage::getDriver();
        $stream = $fs->readStream($id);
        return \Response::stream(function() use($stream) {
            fpassthru($stream);
        }, 200, [
            "Content-Type" => $fs->getMimetype($path),
            "Content-Length" => $fs->getSize($path),
            "Content-disposition" => "attachment; filename=\"" .basename($path) . "\"",
        ]);
    }

but I get

File not found at path: 57

Ideally I'd love to be able to store the S3 url in the db as well as the reference to the image and then just set download attribute on my button with {{ $asset->s3url }} as the href.

To clarify, my assets are stored in the db and S3, renamed to match the db.

Any ideas?

0 likes
10 replies
Snapey's avatar

don't you also need the folder name in the downloadAsset function?

I can see you are writing the file into the 'uploads' bucket, but can't see where you reference that anywhere when downloading?

1stevengrant's avatar

'uploads' isn't actually a bucket, that's a directory within the bucket.

Snapey's avatar

ok, it's a directory. The question is the same.

Snapey's avatar

where does $path come from?

I must admit, I don't know how this type of download works, but is the intention to stream the file or to pass a link back to the browser so that it can come and get it?

As I understand it the attachment-disposition line is just telling the browser, what the file should be called so that the name of the file is preserved between upload and download. It should not contain a folder name.

Anyway, what will $id contain, presumably just the basename of the file? If you are thinking it will be "uploads/monthlyreport.xls" then the router will probably expect those to be separate parameters

If its just the ID of the record in the database, don't you need to find the right record and then get the path to the file from the database?

1stevengrant's avatar

I thought that may have been a variable available to the storage function.

That's just it, the s3 url doesn't get stored in the db. Would be simpler if it did.

1stevengrant's avatar
1stevengrant
OP
Best Answer
Level 6

Got it licked with:

public function downloadAsset($id)
    {
        $asset = Asset::find($id);
        $assetPath = Storage::disk('s3')->url($asset->filename);

        header("Cache-Control: public");
        header("Content-Description: File Transfer");
        header("Content-Disposition: attachment; filename=" . basename($assetPath));
        header("Content-Type: " . $asset->mime);

        return readfile($assetPath);
    }
3 likes
Shahrukh4's avatar

You can give your Content-Type as desired and Content-Disposition as 'attachment' because your files are coming from S3 and you have to download it as an attachment.

$event_data = $this->ticket->where('user_id', $user_id)->first();
        
$data  = $event_data->pdf;

$get_ticket = 'tickets/'. $data;
$file_name  = "YOUR_DESIRED_NAME.pdf";

$headers = [
  'Content-Type'        => 'application/pdf',            
  'Content-Disposition' => 'attachment; filename="'. $file_name .'"',
];

return \Response::make(Storage::disk('s3')->get($get_ticket), 200, $headers);
4 likes

Please or to participate in this conversation.