basic-ftp
Advanced tools
Comparing version 2.7.1 to 2.8.0
# Changelog | ||
## 2.8.0 | ||
- Change uploadDir() so that it reuses directories on the FTP server if they already exist. (#5) | ||
## 2.7.1 | ||
@@ -4,0 +8,0 @@ |
192
lib/ftp.js
@@ -502,4 +502,6 @@ "use strict"; | ||
/** | ||
* Upload the contents of a local directory to the working directory. You can | ||
* optionally provide a `remoteDirName` to put the contents inside a newly created directory. | ||
* Upload the contents of a local directory to the working directory. You can optionally | ||
* provide a `remoteDirName` to put the contents inside a directory which will be created | ||
* if necessary. This will overwrite existing files with the same names and reuse existing | ||
* directories. Unrelated files and directories will remain untouched. | ||
* | ||
@@ -515,6 +517,5 @@ * @param {string} localDirPath A local path, e.g. "foo/bar" or "../test" | ||
} | ||
await this.send("MKD " + remoteDirName); | ||
await this.cd(remoteDirName); | ||
await openDir(this, remoteDirName); | ||
} | ||
await this.uploadDirContents(localDirPath); | ||
await uploadDirContents(this, localDirPath); | ||
// The working directory should stay the same after this operation. | ||
@@ -527,24 +528,2 @@ if (remoteDirName !== undefined) { | ||
/** | ||
* Upload the contents of a local directory to the working directory. | ||
* | ||
* @param {string} localDirPath | ||
*/ | ||
async uploadDirContents(localDirPath) { | ||
const files = await fsReadDir(localDirPath); | ||
for (const file of files) { | ||
const fullPath = path.join(localDirPath, file); | ||
const stats = await fsStat(fullPath); | ||
if (stats.isFile()) { | ||
await this.upload(fs.createReadStream(fullPath), file); | ||
} | ||
else if (stats.isDirectory()) { | ||
await this.send("MKD " + file); | ||
await this.cd(file); | ||
await this.uploadDirContents(fullPath); | ||
await this.send("CDUP"); | ||
} | ||
} | ||
} | ||
/** | ||
* Download all files and directories of the working directory to a local directory. | ||
@@ -571,4 +550,4 @@ * | ||
/** | ||
* Make sure a given remote path exists, creating all directories as necessary. This | ||
* function also changes the current working directory to the given path. | ||
* Make sure a given remote path exists, creating all directories as necessary. | ||
* This function also changes the current working directory to the given path. | ||
* | ||
@@ -578,3 +557,2 @@ * @param {string} remoteDirPath | ||
async ensureDir(remoteDirPath) { | ||
const names = remoteDirPath.split("/").filter(name => name !== ""); | ||
// If the remoteDirPath was absolute go to root directory. | ||
@@ -584,10 +562,5 @@ if (remoteDirPath.startsWith("/")) { | ||
} | ||
const names = remoteDirPath.split("/").filter(name => name !== ""); | ||
for (const name of names) { | ||
// Check first if the directory exists. Just calling MKD might | ||
// result in a permission error for intermediate directories. | ||
const res = await this.send("CWD " + name, true); | ||
if (res.code >= 400) { | ||
await this.send("MKD " + name); | ||
await this.cd(name); | ||
} | ||
await openDir(this, name); | ||
} | ||
@@ -654,2 +627,9 @@ } | ||
/** | ||
* Return true if an FTP return code describes a positive completion. Often it's not | ||
* necessary to know which code it was specifically. | ||
* | ||
* @param {number} code | ||
* @param {boolean} | ||
*/ | ||
function positiveCompletion(code) { | ||
@@ -659,3 +639,58 @@ return code >= 200 && code < 300; | ||
function isSingle(line) { | ||
return /^\d\d\d /.test(line); | ||
} | ||
function isMultiline(line) { | ||
return /^\d\d\d-/.test(line); | ||
} | ||
function describeTLS(socket) { | ||
if (socket.encrypted) { | ||
return socket.getProtocol(); | ||
} | ||
return "No encryption"; | ||
} | ||
/** | ||
* Parse an FTP control response as a collection of messages. A message is a complete | ||
* single- or multiline response. A response can also contain multiple multiline responses | ||
* that will each be represented by a message. A response can also be incomplete | ||
* and be completed on the next incoming data chunk for which case this function also | ||
* describes a `rest`. This function converts all CRLF to LF. | ||
* | ||
* @param {string} text | ||
* @returns {{messages: string[], rest: string}} | ||
*/ | ||
function parseControlResponse(text) { | ||
const lines = text.split(/\r?\n/); | ||
const messages = []; | ||
let startAt = 0; | ||
let token = ""; | ||
for (let i = 0; i < lines.length; i++) { | ||
const line = lines[i]; | ||
// No group has been opened. | ||
if (token === "") { | ||
if (isMultiline(line)) { | ||
// Open a group by setting an expected token. | ||
token = line.substr(0, 3) + " "; | ||
startAt = i; | ||
} | ||
else if (isSingle(line)) { | ||
// Single lines can be grouped immediately. | ||
messages.push(line); | ||
} | ||
} | ||
// Group has been opened, expect closing token. | ||
else if (line.startsWith(token)) { | ||
token = ""; | ||
messages.push(lines.slice(startAt, i + 1).join("\n")); | ||
} | ||
} | ||
// The last group might not have been closed, report it as a rest. | ||
const rest = token !== "" ? lines.slice(startAt).join("\n") + "\n" : ""; | ||
return { messages, rest }; | ||
} | ||
/** | ||
* Upgrade a socket connection with TLS. | ||
@@ -854,9 +889,36 @@ * | ||
function describeTLS(socket) { | ||
if (socket.encrypted) { | ||
return socket.getProtocol(); | ||
/** | ||
* Upload the contents of a local directory to the working directory. This will overwrite | ||
* existing files and reuse existing directories. | ||
* | ||
* @param {string} localDirPath | ||
*/ | ||
async function uploadDirContents(client, localDirPath) { | ||
const files = await fsReadDir(localDirPath); | ||
for (const file of files) { | ||
const fullPath = path.join(localDirPath, file); | ||
const stats = await fsStat(fullPath); | ||
if (stats.isFile()) { | ||
await client.upload(fs.createReadStream(fullPath), file); | ||
} | ||
else if (stats.isDirectory()) { | ||
await openDir(client, file); | ||
await uploadDirContents(client, fullPath); | ||
await client.send("CDUP"); | ||
} | ||
} | ||
return "No encryption"; | ||
} | ||
/** | ||
* Try to create a directory and enter it. This will not raise an exception if the directory | ||
* couldn't be created if for example it already exists. | ||
* | ||
* @param {Client} client | ||
* @param {string} dirName | ||
*/ | ||
async function openDir(client, dirName) { | ||
await client.send("MKD " + dirName, true); // Ignore FTP error codes | ||
await client.cd(dirName); | ||
} | ||
async function ensureLocalDirectory(path) { | ||
@@ -870,49 +932,1 @@ try { | ||
} | ||
/** | ||
* Parse an FTP control response as a collection of messages. A message is a complete | ||
* single- or multiline response. A response can also contain multiple multiline responses | ||
* that will each be represented by a message. A response can also be incomplete | ||
* and be completed on the next incoming data chunk for which case this function also | ||
* describes a `rest`. This function converts all CRLF to LF. | ||
* | ||
* @param {string} text | ||
* @returns {{messages: string[], rest: string}} | ||
*/ | ||
function parseControlResponse(text) { | ||
const lines = text.split(/\r?\n/); | ||
const messages = []; | ||
let startAt = 0; | ||
let token = ""; | ||
for (let i = 0; i < lines.length; i++) { | ||
const line = lines[i]; | ||
// No group has been opened. | ||
if (token === "") { | ||
if (isMultiline(line)) { | ||
// Open a group by setting an expected token. | ||
token = line.substr(0, 3) + " "; | ||
startAt = i; | ||
} | ||
else if (isSingle(line)) { | ||
// Single lines can be grouped immediately. | ||
messages.push(line); | ||
} | ||
} | ||
// Group has been opened, expect closing token. | ||
else if (line.startsWith(token)) { | ||
token = ""; | ||
messages.push(lines.slice(startAt, i + 1).join("\n")); | ||
} | ||
} | ||
// The last group might not have been closed, report it as a rest. | ||
const rest = token !== "" ? lines.slice(startAt).join("\n") + "\n" : ""; | ||
return { messages, rest }; | ||
} | ||
function isSingle(line) { | ||
return /^\d\d\d /.test(line); | ||
} | ||
function isMultiline(line) { | ||
return /^\d\d\d-/.test(line); | ||
} |
{ | ||
"name": "basic-ftp", | ||
"version": "2.7.1", | ||
"version": "2.8.0", | ||
"description": "FTP/FTPS client library", | ||
@@ -5,0 +5,0 @@ "main": "./lib/ftp", |
@@ -58,3 +58,3 @@ # Basic FTP | ||
else { | ||
await client.send("DELE " + file.name); | ||
await client.remove(file.name); | ||
} | ||
@@ -137,3 +137,3 @@ } | ||
Upload all files and directories of a local directory to the current working directory. If you specify a `remoteDirName` it will place the uploads inside a directory of the given name. | ||
Upload all files and directories of a local directory to the current working directory. If you specify a `remoteDirName` it will place the uploads inside a directory of the given name. This will overwrite existing files with the same names and reuse existing directories. Unrelated files and directories will remain untouched. | ||
@@ -140,0 +140,0 @@ `downloadDir(localDirPath): Promise<void>` |
94149
1389