Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for binary file read #458

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions generator/src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,23 @@ function jsonResponse(request, json) {
response: { bodyKind: "json", body: json },
};
}
/**
* @param {any} request
* @param {WithImplicitCoercion<ArrayBuffer | SharedArrayBuffer>} buffer
*/
function bytesResponse(request, buffer) {
return {
request,
response: {
bodyKind: "bytes",
body: Buffer.from(buffer).toString("base64"),
},
};
}

/**
* @param {{ url: string; body: { args: any[] } }} requestToPerform
*/
async function runInternalJob(
requestHash,
app,
Expand All @@ -495,6 +511,11 @@ async function runInternalJob(
requestHash,
await readFileJobNew(requestToPerform, patternsToWatch, context),
];
case "elm-pages-internal://read-file-binary":
return [
requestHash,
await readFileBinaryJobNew(requestToPerform, patternsToWatch),
];
case "elm-pages-internal://glob":
return [
requestHash,
Expand Down Expand Up @@ -576,6 +597,34 @@ async function readFileJobNew(req, patternsToWatch, { cwd }) {
}
}

/**
* @param {{ url: string; body: { args: any[] } }} req
* @param {{ add: (arg0: string) => void; }} patternsToWatch
*/
async function readFileBinaryJobNew(req, patternsToWatch) {
const filePath = req.body.args[1];
try {
patternsToWatch.add(filePath);

const fileContents = await fsPromises.readFile(filePath);
// It's safe to use allocUnsafe here because we're going to overwrite it immediately anyway
const buffer = new Uint8Array(4 + fileContents.length);
const view = new DataView(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength
);
view.setInt32(0, fileContents.length);
fileContents.copy(buffer, 4);

return bytesResponse(req, buffer);
} catch (error) {
const buffer = new Int32Array(1);
buffer[0] = -1;
return bytesResponse(req, buffer);
}
}

function runSleep(req) {
const { milliseconds } = req.body.args[0];
return new Promise((resolve) => {
Expand Down
77 changes: 57 additions & 20 deletions src/BackendTask/File.elm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module BackendTask.File exposing
( bodyWithFrontmatter, bodyWithoutFrontmatter, onlyFrontmatter
, jsonFile, rawFile
, jsonFile, rawFile, binaryFile
, FileReadError(..)
)

Expand Down Expand Up @@ -40,7 +40,7 @@ plain old JSON in Elm.

## Reading Files Without Frontmatter

@docs jsonFile, rawFile
@docs jsonFile, rawFile, binaryFile


## FatalErrors
Expand All @@ -52,6 +52,8 @@ plain old JSON in Elm.
import BackendTask exposing (BackendTask)
import BackendTask.Http
import BackendTask.Internal.Request
import Bytes exposing (Bytes)
import Bytes.Decode
import FatalError exposing (FatalError)
import Json.Decode as Decode exposing (Decoder)
import TerminalText
Expand Down Expand Up @@ -337,6 +339,39 @@ rawFile filePath =
read filePath (Decode.field "rawFile" Decode.string)


{-| Get the raw file content as `Bytes`.

You could read a file called `hello.jpg` in your root project directory like this:

import BackendTask exposing (BackendTask)
import BackendTask.File as File
import Bytes exposing (Bytes)

elmBinaryFile : BackendTask Bytes
elmBinaryFile =
File.binaryFile "hello.jpg"

-}
binaryFile : String -> BackendTask { fatal : FatalError, recoverable : FileReadError decoderError } Bytes
binaryFile filePath =
BackendTask.Internal.Request.request
{ name = "read-file-binary"
, body = BackendTask.Http.stringBody "" filePath
, expect =
Bytes.Decode.signedInt32 Bytes.BE
|> Bytes.Decode.andThen
(\length ->
if length < 0 then
Bytes.Decode.fail

else
Bytes.Decode.bytes length
)
|> BackendTask.Http.expectBytes
}
|> BackendTask.mapError (\_ -> fileNotFound filePath)


{-| Read a file as JSON.

The Decode will strip off any unused JSON data.
Expand Down Expand Up @@ -410,23 +445,25 @@ read filePath decoder =
|> BackendTask.andThen BackendTask.fromResult


errorDecoder :
errorDecoder : String -> Decoder { fatal : FatalError, recoverable : FileReadError decoding }
errorDecoder filePath =
Decode.succeed (fileNotFound filePath)


fileNotFound :
String
->
Decoder
{ fatal : FatalError
, recoverable : FileReadError decoding
}
errorDecoder filePath =
Decode.succeed
(FatalError.recoverable
{ title = "File Doesn't Exist"
, body =
[ TerminalText.text "Couldn't find file at path `"
, TerminalText.yellow filePath
, TerminalText.text "`"
]
|> TerminalText.toString
}
FileDoesntExist
)
{ fatal : FatalError
, recoverable : FileReadError decoding
}
fileNotFound filePath =
FatalError.recoverable
{ title = "File Doesn't Exist"
, body =
[ TerminalText.text "Couldn't find file at path `"
, TerminalText.yellow filePath
, TerminalText.text "`"
]
|> TerminalText.toString
}
FileDoesntExist
Loading