feat(FTP Node): Stream binary data for uploads and downloads (#5296)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-01-31 13:23:04 +01:00 committed by GitHub
parent c7e9a4375f
commit 448c295314
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,4 +1,5 @@
import type { IExecuteFunctions } from 'n8n-core';
import { BINARY_ENCODING } from 'n8n-core';
import type {
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
@ -10,7 +11,12 @@ import type {
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import { createWriteStream } from 'fs';
import { basename, dirname } from 'path';
import type { Readable } from 'stream';
import { pipeline } from 'stream';
import { promisify } from 'util';
import { file as tmpFile } from 'tmp-promise';
import ftpClient from 'promise-ftp';
import sftpClient from 'ssh2-sftp-client';
@ -33,6 +39,8 @@ interface ReturnFtpItem {
path: string;
}
const streamPipeline = promisify(pipeline);
async function callRecursiveList(
path: string,
client: sftpClient | ftpClient,
@ -580,18 +588,22 @@ export class Ftp implements INodeType {
if (operation === 'download') {
const path = this.getNodeParameter('path', i) as string;
const binaryFile = await tmpFile({ prefix: 'n8n-sftp-' });
try {
await sftp!.get(path, createWriteStream(binaryFile.path));
responseData = await sftp!.get(path);
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
const filePathDownload = this.getNodeParameter('path', i) as string;
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
items[i].binary![dataPropertyNameDownload] = await this.helpers.copyBinaryFile(
binaryFile.path,
filePathDownload,
);
const filePathDownload = this.getNodeParameter('path', i) as string;
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
responseData as Buffer,
filePathDownload,
);
returnItems.push(items[i]);
returnItems.push(items[i]);
} finally {
await binaryFile.cleanup();
}
}
if (operation === 'upload') {
@ -609,8 +621,8 @@ export class Ftp implements INodeType {
}
const propertyNameUpload = this.getNodeParameter('binaryPropertyName', i);
if (item.binary[propertyNameUpload] === undefined) {
const itemBinaryData = item.binary[propertyNameUpload];
if (itemBinaryData === undefined) {
throw new NodeOperationError(
this.getNode(),
`No binary data property "${propertyNameUpload}" does not exists on item!`,
@ -618,8 +630,13 @@ export class Ftp implements INodeType {
);
}
const buffer = await this.helpers.getBinaryDataBuffer(i, propertyNameUpload);
await sftp!.put(buffer, remotePath);
let uploadData: Buffer | Readable;
if (itemBinaryData.id) {
uploadData = this.helpers.getBinaryStream(itemBinaryData.id);
} else {
uploadData = Buffer.from(itemBinaryData.data, BINARY_ENCODING);
}
await sftp!.put(uploadData, remotePath);
} else {
// Is text file
const buffer = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8');
@ -669,27 +686,23 @@ export class Ftp implements INodeType {
if (operation === 'download') {
const path = this.getNodeParameter('path', i) as string;
const binaryFile = await tmpFile({ prefix: 'n8n-sftp-' });
try {
const stream = await ftp!.get(path);
await streamPipeline(stream, createWriteStream(binaryFile.path));
responseData = await ftp!.get(path);
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
const filePathDownload = this.getNodeParameter('path', i) as string;
// Convert readable stream to buffer so that can be displayed properly
const chunks = [];
for await (const chunk of responseData) {
chunks.push(chunk);
items[i].binary![dataPropertyNameDownload] = await this.helpers.copyBinaryFile(
binaryFile.path,
filePathDownload,
);
returnItems.push(items[i]);
} finally {
await binaryFile.cleanup();
}
// @ts-ignore
responseData = Buffer.concat(chunks);
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
const filePathDownload = this.getNodeParameter('path', i) as string;
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
responseData,
filePathDownload,
);
returnItems.push(items[i]);
}
if (operation === 'rename') {
@ -718,8 +731,8 @@ export class Ftp implements INodeType {
}
const propertyNameUpload = this.getNodeParameter('binaryPropertyName', i);
if (item.binary[propertyNameUpload] === undefined) {
const itemBinaryData = item.binary[propertyNameUpload];
if (itemBinaryData === undefined) {
throw new NodeOperationError(
this.getNode(),
`No binary data property "${propertyNameUpload}" does not exists on item!`,
@ -727,15 +740,20 @@ export class Ftp implements INodeType {
);
}
const buffer = await this.helpers.getBinaryDataBuffer(i, propertyNameUpload);
let uploadData: Buffer | Readable;
if (itemBinaryData.id) {
uploadData = this.helpers.getBinaryStream(itemBinaryData.id);
} else {
uploadData = Buffer.from(itemBinaryData.data, BINARY_ENCODING);
}
try {
await ftp!.put(buffer, remotePath);
await ftp!.put(uploadData, remotePath);
} catch (error) {
if (error.code === 553) {
// Create directory
await ftp!.mkdir(dirPath, true);
await ftp!.put(buffer, remotePath);
await ftp!.put(uploadData, remotePath);
} else {
throw new NodeApiError(this.getNode(), error);
}