Propogate last modified time

It should've also been possible to use the regular Last-Modified HTTP header,
however that'd have caused a potential loss of precsion if I correctly
understand the string format that we'll need to use.

Refs:
- https://developer.mozilla.org/en-US/docs/Web/API/File/lastModified
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString
- https://nodejs.org/api/fs.html#class-fsstats
This commit is contained in:
Manav Rathi 2024-04-25 11:46:04 +05:30
parent e8e53b2ca5
commit f5ef478a90
No known key found for this signature in database
4 changed files with 45 additions and 31 deletions

View file

@ -62,9 +62,15 @@ const handleRead = async (path: string) => {
// this is binary data.
res.headers.set("Content-Type", "application/octet-stream");
const stat = await fs.stat(path);
// Add the file's size as the Content-Length header.
const fileSize = (await fs.stat(path)).size;
const fileSize = stat.size;
res.headers.set("Content-Length", `${fileSize}`);
// Add the file's last modified time (as epoch milliseconds).
const mtimeMs = stat.mtimeMs;
res.headers.set("X-Last-Modified-Ms", `${mtimeMs}`);
}
return res;
} catch (e) {

View file

@ -34,7 +34,7 @@ type RawEXIFData = Record<string, any> &
ImageHeight: number;
}>;
const EXIF_TAGS_NEEDED = [
const exifTagsNeededForParsingImageMetadata = [
"DateTimeOriginal",
"CreateDate",
"ModifyDate",
@ -53,31 +53,19 @@ const EXIF_TAGS_NEEDED = [
];
/**
* Read EXIF data from an image and use that to construct and return an
* {@link ParsedExtractedMetadata}. This function is tailored for use when we
* upload files.
* Read EXIF data from an image {@link file} and use that to construct and
* return an {@link ParsedExtractedMetadata}.
*
* @param fileOrData The image {@link File}, or its contents.
* This function is tailored for use when we upload files.
*/
export const parseImageMetadata = async (
fileOrData: File | Uint8Array,
file: File,
fileTypeInfo: FileTypeInfo,
): Promise<ParsedExtractedMetadata> => {
/*
if (!(receivedFile instanceof File)) {
receivedFile = new File(
[await receivedFile.blob()],
receivedFile.name,
{
lastModified: receivedFile.lastModified,
},
);
}
*/
const exifData = await getParsedExifData(
fileOrData,
file,
fileTypeInfo,
EXIF_TAGS_NEEDED,
exifTagsNeededForParsingImageMetadata,
);
return {

View file

@ -375,21 +375,32 @@ export const assetName = ({
*/
const readFileOrPath = async (
fileOrPath: File | string,
): Promise<{ dataOrStream: Uint8Array | DataStream; fileSize: number }> => {
): Promise<{
dataOrStream: Uint8Array | DataStream;
fileSize: number;
lastModifiedMs: number;
}> => {
let dataOrStream: Uint8Array | DataStream;
let fileSize: number;
let lastModifiedMs: number;
if (fileOrPath instanceof File) {
const file = fileOrPath;
fileSize = file.size;
lastModifiedMs = file.lastModified;
dataOrStream =
fileSize > MULTIPART_PART_SIZE
? getFileStream(file, FILE_READER_CHUNK_SIZE)
: new Uint8Array(await file.arrayBuffer());
} else {
const path = fileOrPath;
const { response, size } = await readStream(ensureElectron(), path);
const {
response,
size,
lastModifiedMs: lm,
} = await readStream(ensureElectron(), path);
fileSize = size;
lastModifiedMs = lm;
if (size > MULTIPART_PART_SIZE) {
const chunkCount = Math.ceil(size / FILE_READER_CHUNK_SIZE);
dataOrStream = { stream: response.body, chunkCount };
@ -398,7 +409,7 @@ const readFileOrPath = async (
}
}
return { dataOrStream, fileSize };
return { dataOrStream, fileSize, lastModifiedMs };
};
/**

View file

@ -19,18 +19,21 @@ import type { Electron } from "@/next/types/ipc";
* @param path The path on the file on the user's local filesystem whose
* contents we want to stream.
*
* @return A ({@link Response}, size) tuple.
* @return A ({@link Response}, size, lastModifiedMs) triple.
*
* * The response contains the contents of the file. In particular, the `body`
* {@link ReadableStream} property of this response can be used to read the
* files contents in a streaming manner.
*
* * The size is the size of the file that we'll be reading from disk.
*
* * The lastModifiedMs value is the last modified time of the file that we're
* reading, expressed as epoch milliseconds.
*/
export const readStream = async (
_: Electron,
path: string,
): Promise<{ response: Response; size: number }> => {
): Promise<{ response: Response; size: number; lastModifiedMs: number }> => {
const req = new Request(`stream://read${path}`, {
method: "GET",
});
@ -41,13 +44,19 @@ export const readStream = async (
`Failed to read stream from ${path}: HTTP ${res.status}`,
);
const size = +res.headers["Content-Length"];
if (isNaN(size))
throw new Error(
`Got a numeric Content-Length when reading a stream. The response was ${res}`,
);
const size = readNumericHeader(res, "Content-Length");
const lastModifiedMs = readNumericHeader(res, "X-Last-Modified-Ms");
return { response: res, size };
return { response: res, size, lastModifiedMs };
};
const readNumericHeader = (res: Response, key: string) => {
const value = +res.headers[key];
if (isNaN(value))
throw new Error(
`Expected a numeric ${key} when reading a stream response: ${res}`,
);
return value;
};
/**