Skip to content

Commit

Permalink
Add AsyncDispoable interfaces to streams.
Browse files Browse the repository at this point in the history
- Also update examples.
  • Loading branch information
nullobsi committed Dec 23, 2023
1 parent 417a4b2 commit a69e9bf
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 28 deletions.
36 changes: 28 additions & 8 deletions classes/FTPClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { FTPFileInfo } from "../types/FTPFileInfo.ts";
import FTPReply from "../types/FTPReply.ts";

export class FTPClient implements Deno.Closer {
export class FTPClient implements AsyncDisposable, Disposable {
private conn?: Deno.Conn;
private connLineReader?: ReadableStream<string>;

Expand Down Expand Up @@ -74,6 +74,15 @@ export class FTPClient implements Deno.Closer {
this.opts = n;
}

// Not sure if implementing both is the way to go...
[Symbol.dispose](): void {
this.close();
}

async [Symbol.asyncDispose](): Promise<void> {
await this.close();
}

private static notInit() {
return new Error("Connection not initialized!");
}
Expand Down Expand Up @@ -232,9 +241,10 @@ export class FTPClient implements Deno.Closer {
/**
* Download a file from the server using a ReadableStream interface.
* **Please call FTPClient.finalizeStream** to release the lock
* after the file is downloaded.
* after the file is downloaded. Or, you can use the AsyncDispoable
* interface.
*/
public async downloadReadable(fileName: string): Promise<ReadableStream> {
public async downloadReadable(fileName: string): Promise<ReadableStream<Uint8Array> & AsyncDisposable> {
await this.lock.lock();
if (this.conn === undefined) {
this.lock.unlock();
Expand All @@ -257,7 +267,12 @@ export class FTPClient implements Deno.Closer {
}

const conn = await this.finalizeDataConnection();
return conn.readable;

return Object.assign(conn.readable, {
[Symbol.asyncDispose]: async () => {
await this.finalizeStream();
}
});
}

/**
Expand Down Expand Up @@ -293,11 +308,12 @@ export class FTPClient implements Deno.Closer {
/**
* Upload a file using a WritableStream interface.
* **Please call FTPClient.finalizeStream()** to release the lock after
* the file is uploaded.
* the file is uploaded. Or, you can use the AsyncDispoable
* interface.
* @param fileName
* @param allocate Number of bytes to allocate to the file. Some servers require this parameter.
*/
public async uploadWritable(fileName: string, allocate?: number): Promise<WritableStream> {
public async uploadWritable(fileName: string, allocate?: number): Promise<WritableStream<Uint8Array> & AsyncDisposable> {
await this.lock.lock();
if (this.conn === undefined) {
this.lock.unlock();
Expand Down Expand Up @@ -334,7 +350,11 @@ export class FTPClient implements Deno.Closer {

const conn = await this.finalizeDataConnection();

return conn.writable;
return Object.assign(conn.writable, {
[Symbol.asyncDispose]: async () => {
await this.finalizeStream();
}
});
}

/**
Expand Down Expand Up @@ -891,7 +911,7 @@ export class FTPClient implements Deno.Closer {
private assertStatus(
expected: StatusCodes,
result: FTPReply,
...resources: (Deno.Closer | undefined)[]
...resources: (Disposable | undefined)[]
) {
if (result.code !== expected) {
const errors: Error[] = [];
Expand Down
29 changes: 17 additions & 12 deletions examples/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@
import { FTPClient } from "../mod.ts";

// Connect as anonymous user
const client = new FTPClient("speedtest.tele2.net");
using client = new FTPClient("speedtest.tele2.net");

await client.connect();
console.log("Connected!");

// Download test file
console.log("Downloading...");
const file = await Deno.open("./5MB.zip", {
create: true,
write: true,
});
const stream = await client.downloadReadable("5MB.zip");
await stream.pipeTo(file.writable);

// Close download stream. File is already closed by pipeTo method.
await client.finalizeStream();

{
using file = await Deno.open("./5MB.zip", {
create: true,
write: true,
});

// Use Readable and Writable interface for fast and easy tranfers.
await using stream = await client.downloadReadable("5MB.zip");
await stream.pipeTo(file.writable);
} // Because of `await using`, finalizeStream is called and server is notified.

// File is already closed by pipeTo method.
console.log("Finished!");

// Log off server
await client.close();
// Since we did `using`, connection is automatically closed.

12 changes: 6 additions & 6 deletions examples/upload.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FTPClient } from "../mod.ts";

// Create a connection to an FTP server
const client = new FTPClient("ftp.server", {
using client = new FTPClient("ftp.server", {
// Enable TLS
tlsOpts: {
implicit: false,
Expand All @@ -28,11 +28,10 @@ crypto.getRandomValues(randomData);
await client.chdir("files");

// Create a stream to upload the file random.bin with a size of 4096 bytes
const uploadStream = await client.uploadWritable("random.bin", 4096);
uploadStream.getWriter().write(randomData);

// Close the stream and notify the server that file upload is complete
await client.finalizeStream();
{
await using uploadStream = await client.uploadWritable("random.bin", 4096);
await uploadStream.getWriter().write(randomData);
}

// Redownload the file from the server
const downloadedData = new Uint8Array(await client.download("random.bin"));
Expand All @@ -45,3 +44,4 @@ for (let i = 0; i < randomData.length; i++) {
console.log(`Files are not the same at ${i}!`);
}
}

4 changes: 2 additions & 2 deletions util/free.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export default function free(resource: Deno.Closer | undefined) {
export default function free(resource: Disposable | undefined) {
if (resource) {
try {
resource.close();
resource[Symbol.dispose]();
} catch (e) {
if (e instanceof Deno.errors.BadResource) {
return;
Expand Down

0 comments on commit a69e9bf

Please sign in to comment.