basic-ftp
Advanced tools
Comparing version 4.2.1 to 4.3.0
# Changelog | ||
## 4.3.0 | ||
- Added: More explicit API `uploadFrom`, `appendFrom` and `downloadTo`. `upload` and `download` are still available but deprecated. | ||
- Added: Handle file downloads and uploads directly by supporting local file paths in `uploadFrom` and `downloadTo`. | ||
- Added: Make it easier to resume a download of a partially downloaded file. See documentation of `downloadTo` for more details. | ||
## 4.2.1 | ||
@@ -4,0 +10,0 @@ |
@@ -7,2 +7,3 @@ /// <reference types="node" /> | ||
import { ProgressHandler, ProgressTracker } from "./ProgressTracker"; | ||
import { UploadCommand } from "./transfer"; | ||
export interface AccessOptions { | ||
@@ -26,2 +27,8 @@ /** Host the client should connect to. Optional, default is "localhost". */ | ||
export declare type RawListParser = (rawList: string) => FileInfo[]; | ||
export interface UploadOptions { | ||
/** Offset in the local file to start uploading from. */ | ||
localStart?: number; | ||
/** Final byte position to include in upload from the local file. */ | ||
localEndInclusive?: number; | ||
} | ||
/** | ||
@@ -169,32 +176,54 @@ * High-level API to interact with an FTP server. | ||
/** | ||
* Upload data from a readable stream and store it as a file with a given filename in the current working directory. | ||
* If such a file already exists it will be overwritten. | ||
* Upload data from a readable stream or a local file to a remote file. | ||
* | ||
* @param source The stream to read from. | ||
* @param remotePath The path of the remote file to write to. | ||
* @param source Readable stream or path to a local file. | ||
* @param toRemotePath Path to a remote file to write to. | ||
*/ | ||
upload(source: Readable, remotePath: string): Promise<FTPResponse>; | ||
uploadFrom(source: Readable | string, toRemotePath: string, options?: UploadOptions): Promise<FTPResponse>; | ||
/** | ||
* Upload data from a readable stream and append it to an existing file with a given filename in the current working directory. | ||
* If the file doesn't exist the FTP server should create it. | ||
* DEPRECATED, use `uploadFrom`. | ||
* @deprecated | ||
*/ | ||
upload(source: Readable | string, toRemotePath: string, options?: UploadOptions): Promise<FTPResponse>; | ||
/** | ||
* Upload data from a readable stream or a local file by appending it to an existing file. If the file doesn't | ||
* exist the FTP server should create it. | ||
* | ||
* @param source The stream to read from. | ||
* @param remotePath The path of the existing remote file to append to. | ||
* @param source Readable stream or path to a local file. | ||
* @param toRemotePath Path to a remote file to write to. | ||
*/ | ||
append(source: Readable, remotePath: string): Promise<FTPResponse>; | ||
appendFrom(source: Readable | string, toRemotePath: string, options?: UploadOptions): Promise<FTPResponse>; | ||
/** | ||
* DEPRECATED, use `appendFrom`. | ||
* @deprecated | ||
*/ | ||
append(source: Readable | string, toRemotePath: string, options?: UploadOptions): Promise<FTPResponse>; | ||
protected _uploadWithCommand(source: Readable | string, remotePath: string, command: UploadCommand, options: UploadOptions): Promise<FTPResponse>; | ||
protected _uploadLocalFile(localPath: string, remotePath: string, command: UploadCommand, options: UploadOptions): Promise<FTPResponse>; | ||
/** | ||
* @protected | ||
*/ | ||
protected _uploadWithCommand(source: Readable, remotePath: string, command: "STOR" | "APPE"): Promise<FTPResponse>; | ||
protected _uploadFromStream(source: Readable, remotePath: string, command: UploadCommand): Promise<FTPResponse>; | ||
/** | ||
* Download a file with a given filename from the current working directory | ||
* and pipe its data to a writable stream. You may optionally start at a specific | ||
* offset, for example to resume a cancelled transfer. | ||
* Download a remote file and pipe its data to a writable stream or to a local file. | ||
* | ||
* @param destination The stream to write to. | ||
* @param remotePath The name of the remote file to read from. | ||
* @param startAt The offset to start at. | ||
* You can optionally define at which position of the remote file you'd like to start | ||
* downloading. If the destination you provide is a file, the offset will be applied | ||
* to it as well. For example: To resume a failed download, you'd request the size of | ||
* the local, partially downloaded file and use that as the offset. Assuming the size | ||
* is 23, you'd download the rest using `downloadTo("local.txt", "remote.txt", 23)`. | ||
* | ||
* @param destination Stream or path for a local file to write to. | ||
* @param fromRemotePath Path of the remote file to read from. | ||
* @param startAt Position within the remote file to start downloading at. If the destination is a file, this offset is also applied to it. | ||
*/ | ||
download(destination: Writable, remotePath: string, startAt?: number): Promise<FTPResponse>; | ||
downloadTo(destination: Writable | string, fromRemotePath: string, startAt?: number): Promise<FTPResponse>; | ||
/** | ||
* DEPRECATED, use `downloadTo`. | ||
* @deprecated | ||
*/ | ||
download(destination: Writable | string, fromRemotePath: string, startAt?: number): Promise<FTPResponse>; | ||
protected _downloadToFile(localPath: string, remotePath: string, startAt: number): Promise<FTPResponse>; | ||
protected _downloadToStream(destination: Writable, remotePath: string, startAt: number): Promise<FTPResponse>; | ||
/** | ||
* List files and directories in the current working directory, or from `path` if specified. | ||
@@ -201,0 +230,0 @@ * |
@@ -19,2 +19,5 @@ "use strict"; | ||
const fsStat = util_1.promisify(fs_1.stat); | ||
const fsOpen = util_1.promisify(fs_1.open); | ||
const fsClose = util_1.promisify(fs_1.close); | ||
const fsUnlink = util_1.promisify(fs_1.unlink); | ||
/** | ||
@@ -301,25 +304,59 @@ * High-level API to interact with an FTP server. | ||
/** | ||
* Upload data from a readable stream and store it as a file with a given filename in the current working directory. | ||
* If such a file already exists it will be overwritten. | ||
* Upload data from a readable stream or a local file to a remote file. | ||
* | ||
* @param source The stream to read from. | ||
* @param remotePath The path of the remote file to write to. | ||
* @param source Readable stream or path to a local file. | ||
* @param toRemotePath Path to a remote file to write to. | ||
*/ | ||
async upload(source, remotePath) { | ||
return this._uploadWithCommand(source, remotePath, "STOR"); | ||
async uploadFrom(source, toRemotePath, options = {}) { | ||
return this._uploadWithCommand(source, toRemotePath, "STOR", options); | ||
} | ||
/** | ||
* Upload data from a readable stream and append it to an existing file with a given filename in the current working directory. | ||
* If the file doesn't exist the FTP server should create it. | ||
* DEPRECATED, use `uploadFrom`. | ||
* @deprecated | ||
*/ | ||
async upload(source, toRemotePath, options = {}) { | ||
return this.uploadFrom(source, toRemotePath, options); | ||
} | ||
/** | ||
* Upload data from a readable stream or a local file by appending it to an existing file. If the file doesn't | ||
* exist the FTP server should create it. | ||
* | ||
* @param source The stream to read from. | ||
* @param remotePath The path of the existing remote file to append to. | ||
* @param source Readable stream or path to a local file. | ||
* @param toRemotePath Path to a remote file to write to. | ||
*/ | ||
async append(source, remotePath) { | ||
return this._uploadWithCommand(source, remotePath, "APPE"); | ||
async appendFrom(source, toRemotePath, options = {}) { | ||
return this._uploadWithCommand(source, toRemotePath, "APPE", options); | ||
} | ||
/** | ||
* DEPRECATED, use `appendFrom`. | ||
* @deprecated | ||
*/ | ||
async append(source, toRemotePath, options = {}) { | ||
return this.appendFrom(source, toRemotePath, options); | ||
} | ||
async _uploadWithCommand(source, remotePath, command, options) { | ||
if (typeof source === "string") { | ||
return this._uploadLocalFile(source, remotePath, command, options); | ||
} | ||
return this._uploadFromStream(source, remotePath, command); | ||
} | ||
async _uploadLocalFile(localPath, remotePath, command, options) { | ||
const fd = await fsOpen(localPath, "r"); | ||
const source = fs_1.createReadStream("", { | ||
fd, | ||
start: options.localStart, | ||
end: options.localEndInclusive, | ||
autoClose: false | ||
}); | ||
try { | ||
return await this._uploadFromStream(source, remotePath, command); | ||
} | ||
finally { | ||
await ignoreError(() => fsClose(fd)); | ||
} | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
async _uploadWithCommand(source, remotePath, command) { | ||
async _uploadFromStream(source, remotePath, command) { | ||
const onError = (err) => this.ftp.closeWithError(err); | ||
@@ -339,11 +376,50 @@ source.once("error", onError); | ||
/** | ||
* Download a file with a given filename from the current working directory | ||
* and pipe its data to a writable stream. You may optionally start at a specific | ||
* offset, for example to resume a cancelled transfer. | ||
* Download a remote file and pipe its data to a writable stream or to a local file. | ||
* | ||
* @param destination The stream to write to. | ||
* @param remotePath The name of the remote file to read from. | ||
* @param startAt The offset to start at. | ||
* You can optionally define at which position of the remote file you'd like to start | ||
* downloading. If the destination you provide is a file, the offset will be applied | ||
* to it as well. For example: To resume a failed download, you'd request the size of | ||
* the local, partially downloaded file and use that as the offset. Assuming the size | ||
* is 23, you'd download the rest using `downloadTo("local.txt", "remote.txt", 23)`. | ||
* | ||
* @param destination Stream or path for a local file to write to. | ||
* @param fromRemotePath Path of the remote file to read from. | ||
* @param startAt Position within the remote file to start downloading at. If the destination is a file, this offset is also applied to it. | ||
*/ | ||
async download(destination, remotePath, startAt = 0) { | ||
async downloadTo(destination, fromRemotePath, startAt = 0) { | ||
if (typeof destination === "string") { | ||
return this._downloadToFile(destination, fromRemotePath, startAt); | ||
} | ||
return this._downloadToStream(destination, fromRemotePath, startAt); | ||
} | ||
/** | ||
* DEPRECATED, use `downloadTo`. | ||
* @deprecated | ||
*/ | ||
async download(destination, fromRemotePath, startAt = 0) { | ||
return this.downloadTo(destination, fromRemotePath, startAt); | ||
} | ||
async _downloadToFile(localPath, remotePath, startAt) { | ||
const expectLocalFile = startAt > 0; | ||
const fileSystemFlags = expectLocalFile ? "r+" : "w"; | ||
const fd = await fsOpen(localPath, fileSystemFlags); | ||
const destination = fs_1.createWriteStream("", { | ||
fd, | ||
start: startAt, | ||
autoClose: false | ||
}); | ||
try { | ||
return await this._downloadToStream(destination, remotePath, startAt); | ||
} | ||
catch (err) { | ||
if (!expectLocalFile) { | ||
await ignoreError(() => fsUnlink(localPath)); | ||
} | ||
throw err; | ||
} | ||
finally { | ||
await ignoreError(() => fsClose(fd)); | ||
} | ||
} | ||
async _downloadToStream(destination, remotePath, startAt) { | ||
const onError = (err) => this.ftp.closeWithError(err); | ||
@@ -478,4 +554,3 @@ destination.once("error", onError); | ||
else { | ||
const writable = fs_1.createWriteStream(localPath); | ||
await this.download(writable, file.name); | ||
await this.downloadTo(localPath, file.name); | ||
} | ||
@@ -561,3 +636,3 @@ } | ||
if (stats.isFile()) { | ||
await client.upload(fs_1.createReadStream(fullPath), file); | ||
await client.uploadFrom(fullPath, file); | ||
} | ||
@@ -584,4 +659,12 @@ else if (stats.isDirectory()) { | ||
catch (err) { | ||
await fsMkDir(path); | ||
await fsMkDir(path, { recursive: true }); | ||
} | ||
} | ||
async function ignoreError(func) { | ||
try { | ||
await func(); | ||
} | ||
catch (err) { | ||
// Ignore | ||
} | ||
} |
@@ -5,2 +5,3 @@ /// <reference types="node" /> | ||
import { ProgressTracker } from "./ProgressTracker"; | ||
export declare type UploadCommand = "STOR" | "APPE"; | ||
/** | ||
@@ -31,3 +32,3 @@ * Prepare a data socket using passive mode over IPv6. | ||
*/ | ||
export declare function upload(ftp: FTPContext, progress: ProgressTracker, source: Readable, command: "STOR" | "APPE", remoteFilename: string): Promise<FTPResponse>; | ||
export declare function upload(ftp: FTPContext, progress: ProgressTracker, source: Readable, command: UploadCommand, remoteFilename: string): Promise<FTPResponse>; | ||
/** | ||
@@ -34,0 +35,0 @@ * Download data from the data connection. Used for downloading files and directory listings. |
{ | ||
"name": "basic-ftp", | ||
"version": "4.2.1", | ||
"version": "4.3.0", | ||
"description": "FTP client for Node.js, supports explicit FTPS over TLS, IPv6, Async/Await, and Typescript.", | ||
@@ -39,8 +39,8 @@ "main": "dist/index", | ||
"devDependencies": { | ||
"@types/node": "12.7.12", | ||
"@typescript-eslint/eslint-plugin": "2.3.3", | ||
"@typescript-eslint/parser": "2.3.3", | ||
"@types/node": "12.11.1", | ||
"@typescript-eslint/eslint-plugin": "2.4.0", | ||
"@typescript-eslint/parser": "2.4.0", | ||
"eslint": "6.5.1", | ||
"js-yaml": ">=3.13.1", | ||
"mocha": "6.2.1", | ||
"mocha": "6.2.2", | ||
"rimraf": "3.0.0", | ||
@@ -47,0 +47,0 @@ "typescript": "3.6.4" |
@@ -17,7 +17,6 @@ # Basic FTP | ||
The first example will connect to an FTP server using TLS, get a directory listing, and upload a file. Note that the FTP protocol doesn't allow multiple requests running in parallel. | ||
The first example will connect to an FTP server using TLS, get a directory listing, upload a file and download it as a copy. Note that the FTP protocol doesn't allow multiple requests running in parallel. | ||
```js | ||
const ftp = require("basic-ftp") | ||
const fs = require("fs") | ||
@@ -37,3 +36,4 @@ example() | ||
console.log(await client.list()) | ||
await client.upload(fs.createReadStream("README.md"), "README.md") | ||
await client.uploadFrom("README.md", "README_FTP.md") | ||
await client.downloadTo("README_COPY.md", "README_FTP.md") | ||
} | ||
@@ -94,11 +94,11 @@ catch(err) { | ||
Send an FTP command. | ||
Send an FTP command and return the first response. | ||
`sendIgnoringError(command): Promise<FTPResponse>` | ||
Send an FTP command and ignore an FTP error response. Any other error or timeout will still reject the Promise. | ||
Send an FTP command, return the first response, and ignore an FTP error response. Any other error or timeout will still reject the Promise. | ||
`cd(remotePath): Promise<FTPResponse>` | ||
`cd(path): Promise<FTPResponse>` | ||
Change the working directory. | ||
Change the current working directory. | ||
@@ -111,11 +111,11 @@ `pwd(): Promise<string>` | ||
List files and directories in the current working directory, or from `path` if specified. Currently, this library only supports MLSD, Unix and DOS directory listings. See [FileInfo](src/FileInfo.ts) for more details. | ||
List files and directories in the current working directory, or at `path` if specified. Currently, this library only supports MLSD, Unix and DOS directory listings. See [FileInfo](src/FileInfo.ts) for more details. | ||
`lastMod(filename): Promise<Date>` | ||
`lastMod(path): Promise<Date>` | ||
Get the last modification time of a file in the working directory. This command might not be supported by your FTP server and throw an exception. | ||
Get the last modification time of a file. This command might not be supported by your FTP server and throw an exception. | ||
`size(filename): Promise<number>` | ||
`size(path): Promise<number>` | ||
Get the size of a file in the working directory. | ||
Get the size of a file in bytes. | ||
@@ -126,17 +126,17 @@ `rename(path, newPath): Promise<FTPResponse>` | ||
`remove(filename): Promise<FTPResponse>` | ||
`remove(path): Promise<FTPResponse>` | ||
Remove a file from the working directory. | ||
Remove a file. | ||
`upload(readableStream, remoteFilename): Promise<FTPResponse>` | ||
`uploadFrom(readableStream | localPath, remotePath): Promise<FTPResponse>` | ||
Upload data from a readable stream and store it as a file with a given filename in the current working directory. If such a file already exists it will be overwritten. | ||
Upload data from a readable stream or a local file to a remote file. If such a file already exists it will be overwritten. | ||
`append(readableStream, remoteFilename): Promise<FTPResponse>` | ||
`appendFrom(readableStream | localPath, remotePath): Promise<FTPResponse>` | ||
Upload data from a readable stream and append it to an existing file with a given filename in the current working directory. If the file doesn't exist the FTP server should create it. | ||
Upload data from a readable stream or a local file by appending it to an existing file. If the file doesn't exist the FTP server should create it. | ||
`download(writableStream, remoteFilename, startAt = 0): Promise<FTPResponse>` | ||
`downloadTo(writableStream | localPath, remotePath, startAt = 0): Promise<FTPResponse>` | ||
Download a file with a given filename from the current working directory and pipe its data to a writable stream. You may optionally start at a specific offset, for example to resume a cancelled transfer. | ||
Download a remote file and pipe its data to a writable stream or to a local file. You can optionally define at which position of the remote file you'd like to start downloading. If the destination you provide is a file, the offset will be applied to it as well. For example: To resume a failed download, you'd request the size of the local, partially downloaded file and use that as the offset. | ||
@@ -143,0 +143,0 @@ --- |
137687
2811