mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -08:00
feat(FTP Node): Stream binary data for uploads and downloads (#5296)
This commit is contained in:
parent
c7e9a4375f
commit
448c295314
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue