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

fix(ext/node): add truncate method to the FileHandle class #27389

Merged
merged 6 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
18 changes: 13 additions & 5 deletions ext/node/polyfills/_fs/_fs_ftruncate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,24 @@ export function ftruncate(
: undefined;
const callback: CallbackWithError = typeof lenOrCallback === "function"
? lenOrCallback
: maybeCallback as CallbackWithError;
: (maybeCallback as CallbackWithError);

if (!callback) throw new Error("No callback function supplied");

new FsFile(fd, Symbol.for("Deno.internal.FsFile")).truncate(len).then(
() => callback(null),
callback,
);
new FsFile(fd, Symbol.for("Deno.internal.FsFile"))
.truncate(len)
.then(() => callback(null), callback);
}

export function ftruncateSync(fd: number, len?: number) {
new FsFile(fd, Symbol.for("Deno.internal.FsFile")).truncateSync(len);
}

export function ftruncatePromise(fd: number, len?: number): Promise<void> {
return new Promise((resolve, reject) => {
ftruncate(fd, len, (err) => {
if (err) reject(err);
else resolve();
});
});
}
7 changes: 6 additions & 1 deletion ext/node/polyfills/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import { exists, existsSync } from "ext:deno_node/_fs/_fs_exists.ts";
import { fdatasync, fdatasyncSync } from "ext:deno_node/_fs/_fs_fdatasync.ts";
import { fstat, fstatPromise, fstatSync } from "ext:deno_node/_fs/_fs_fstat.ts";
import { fsync, fsyncSync } from "ext:deno_node/_fs/_fs_fsync.ts";
import { ftruncate, ftruncateSync } from "ext:deno_node/_fs/_fs_ftruncate.ts";
import {
ftruncate,
ftruncatePromise,
ftruncateSync,
} from "ext:deno_node/_fs/_fs_ftruncate.ts";
import { futimes, futimesSync } from "ext:deno_node/_fs/_fs_futimes.ts";
import {
lchown,
Expand Down Expand Up @@ -165,6 +169,7 @@ const promises = {
opendir: opendirPromise,
rename: renamePromise,
truncate: truncatePromise,
ftruncate: ftruncatePromise,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node doesn't seem having fs.promises.ftruncate (No doc entry for it, and also not available in runtime) https://nodejs.org/api/fs.html

Let's not expose it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You make a great point! I've noticed that fstat is also exposed but can't seem to find it in the doc, and fs.promises.fstat also doesn't seem to be available in the runtime. Is that an issue, and if so, is it something we would like to handle within this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fstat is also exposed but can't seem to find it in the doc, and fs.promises.fstat also doesn't seem to be available in the runtime. Is that an issue

Good point. Yes, that seems an issue to me.

, and if so, is it something we would like to handle within this PR?

Fixing it in this PR is welcome, but that's not absolutely necessary in my opinon. I created an issue for fs.promises.fstat for now #27423

Copy link
Contributor Author

@filipstev filipstev Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, I might take a deeper look into that one as well, see if we might have more to clean up :)

Also, the implementation for promises.ftruncate has been cleaned up 🧹

rm: rmPromise,
rmdir: rmdirPromise,
mkdir: mkdirPromise,
Expand Down
1 change: 1 addition & 0 deletions ext/node/polyfills/fs/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const open = fsPromises.open;
export const opendir = fsPromises.opendir;
export const rename = fsPromises.rename;
export const truncate = fsPromises.truncate;
export const ftruncate = fsPromises.ftruncate;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. Let's not expose this

export const rm = fsPromises.rm;
export const rmdir = fsPromises.rmdir;
export const mkdir = fsPromises.mkdir;
Expand Down
24 changes: 9 additions & 15 deletions ext/node/polyfills/internal/fs/handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export class FileHandle extends EventEmitter {
}
}

truncate(len?: number): Promise<void> {
return fsCall(promises.ftruncate, this, len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we need to import ftruncatePromise directly from "ext:deno_node/_fs/_fs_ftruncate.ts"

}

readFile(
opt?: TextOptionsArgument | BinaryOptionsArgument | FileOptionsArgument,
): Promise<string | Buffer> {
Expand All @@ -85,11 +89,7 @@ export class FileHandle extends EventEmitter {
length: number,
position: number,
): Promise<WriteResult>;
write(
str: string,
position: number,
encoding: string,
): Promise<WriteResult>;
write(str: string, position: number, encoding: string): Promise<WriteResult>;
write(
bufferOrStr: Uint8Array | string,
offsetOrPosition: number,
Expand Down Expand Up @@ -120,16 +120,10 @@ export class FileHandle extends EventEmitter {
const encoding = lengthOrEncoding;

return new Promise((resolve, reject) => {
write(
this.fd,
str,
position,
encoding,
(err, bytesWritten, buffer) => {
if (err) reject(err);
else resolve({ buffer, bytesWritten });
},
);
write(this.fd, str, position, encoding, (err, bytesWritten, buffer) => {
if (err) reject(err);
else resolve({ buffer, bytesWritten });
});
});
}
}
Expand Down
82 changes: 82 additions & 0 deletions tests/unit_node/_fs/_fs_handle_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,85 @@ Deno.test("[node/fs filehandle.writeFile] Write to file", async function () {

assertEquals(decoder.decode(data), "hello world");
});

Deno.test(
"[node/fs filehandle.truncate] Truncate file with length",
async function () {
const tempFile: string = await Deno.makeTempFile();
const fileHandle = await fs.open(tempFile, "w+");

await fileHandle.writeFile("hello world");

await fileHandle.truncate(5);

const data = Deno.readFileSync(tempFile);
await Deno.remove(tempFile);
await fileHandle.close();

assertEquals(decoder.decode(data), "hello");
},
);

Deno.test(
"[node/fs filehandle.truncate] Truncate file without length",
async function () {
const tempFile: string = await Deno.makeTempFile();
const fileHandle = await fs.open(tempFile, "w+");

await fileHandle.writeFile("hello world");

await fileHandle.truncate();

const data = Deno.readFileSync(tempFile);
await Deno.remove(tempFile);
await fileHandle.close();

assertEquals(decoder.decode(data), "");
},
);

Deno.test(
"[node/fs filehandle.truncate] Truncate file with extension",
async function () {
const tempFile: string = await Deno.makeTempFile();
const fileHandle = await fs.open(tempFile, "w+");

await fileHandle.writeFile("hi");

await fileHandle.truncate(5);

const data = Deno.readFileSync(tempFile);
await Deno.remove(tempFile);
await fileHandle.close();

const expected = new Uint8Array(5);
expected.set(new TextEncoder().encode("hi"));

assertEquals(data, expected);
assertEquals(data.length, 5);
assertEquals(decoder.decode(data.subarray(0, 2)), "hi");
// Verify null bytes
assertEquals(data[2], 0);
assertEquals(data[3], 0);
assertEquals(data[4], 0);
},
);

Deno.test(
"[node/fs filehandle.truncate] Truncate file with negative length",
async function () {
const tempFile: string = await Deno.makeTempFile();
const fileHandle = await fs.open(tempFile, "w+");

await fileHandle.writeFile("hello world");

await fileHandle.truncate(-1);

const data = Deno.readFileSync(tempFile);
await Deno.remove(tempFile);
await fileHandle.close();

assertEquals(decoder.decode(data), "");
assertEquals(data.length, 0);
},
);
Loading