From a69e9bf661e55e82015b46f01ab3f2330a04d5c1 Mon Sep 17 00:00:00 2001 From: Citlali del Rey Date: Sat, 23 Dec 2023 12:37:03 -0800 Subject: [PATCH] Add AsyncDispoable interfaces to streams. - Also update examples. --- classes/FTPClient.ts | 36 ++++++++++++++++++++++++++++-------- examples/download.ts | 29 +++++++++++++++++------------ examples/upload.ts | 12 ++++++------ util/free.ts | 4 ++-- 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/classes/FTPClient.ts b/classes/FTPClient.ts index abc4230..a07f255 100644 --- a/classes/FTPClient.ts +++ b/classes/FTPClient.ts @@ -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; @@ -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 { + await this.close(); + } + private static notInit() { return new Error("Connection not initialized!"); } @@ -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 { + public async downloadReadable(fileName: string): Promise & AsyncDisposable> { await this.lock.lock(); if (this.conn === undefined) { this.lock.unlock(); @@ -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(); + } + }); } /** @@ -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 { + public async uploadWritable(fileName: string, allocate?: number): Promise & AsyncDisposable> { await this.lock.lock(); if (this.conn === undefined) { this.lock.unlock(); @@ -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(); + } + }); } /** @@ -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[] = []; diff --git a/examples/download.ts b/examples/download.ts index ad331c7..e6be5ff 100644 --- a/examples/download.ts +++ b/examples/download.ts @@ -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. + diff --git a/examples/upload.ts b/examples/upload.ts index 6deed68..74d9384 100644 --- a/examples/upload.ts +++ b/examples/upload.ts @@ -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, @@ -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")); @@ -45,3 +44,4 @@ for (let i = 0; i < randomData.length; i++) { console.log(`Files are not the same at ${i}!`); } } + diff --git a/util/free.ts b/util/free.ts index 4f9477c..6c28e07 100644 --- a/util/free.ts +++ b/util/free.ts @@ -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;