From e437dace7069744c26eb1cce368f07d31f363ab5 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 4 Aug 2025 12:40:18 +0100 Subject: [PATCH] feat(FTP Node): Add support for concurrent reads when using SFTP (#17896) Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Michael Kret --- packages/nodes-base/nodes/Ftp/Ftp.node.ts | 67 ++++++++++++++++++++++- packages/nodes-base/package.json | 4 +- pnpm-lock.yaml | 41 +++++++++----- 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/packages/nodes-base/nodes/Ftp/Ftp.node.ts b/packages/nodes-base/nodes/Ftp/Ftp.node.ts index b7070dab52..b4dc2c6691 100644 --- a/packages/nodes-base/nodes/Ftp/Ftp.node.ts +++ b/packages/nodes-base/nodes/Ftp/Ftp.node.ts @@ -289,7 +289,50 @@ export class Ftp implements INodeType { hint: 'The name of the output binary field to put the file in', required: true, }, - + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: ['download'], + }, + }, + options: [ + { + displayName: 'Enable Concurrent Reads', + name: 'enableConcurrentReads', + type: 'boolean', + default: false, + description: 'Whether to enable concurrent reads for downloading files', + }, + { + displayName: 'Max Concurrent Reads', + name: 'maxConcurrentReads', + type: 'number', + default: 5, + displayOptions: { + show: { + enableConcurrentReads: [true], + }, + }, + }, + { + displayName: 'Chunk Size', + name: 'chunkSize', + type: 'number', + default: 64, + description: 'Size of each chunk in KB to download, Not all servers support this', + displayOptions: { + show: { + enableConcurrentReads: [true], + }, + }, + }, + ], + }, // ---------------------------------- // rename // ---------------------------------- @@ -530,6 +573,10 @@ export class Ftp implements INodeType { password: (credentials.password as string) || undefined, privateKey: formatPrivateKey(credentials.privateKey as string), passphrase: credentials.passphrase as string | undefined, + readyTimeout: 10000, + algorithms: { + compress: ['zlib@openssh.com', 'zlib', 'none'], + }, }); } else { await sftp.connect({ @@ -537,6 +584,10 @@ export class Ftp implements INodeType { port: credentials.port as number, username: credentials.username as string, password: credentials.password as string, + readyTimeout: 10000, + algorithms: { + compress: ['zlib@openssh.com', 'zlib', 'none'], + }, }); } } else { @@ -632,9 +683,21 @@ export class Ftp implements INodeType { if (operation === 'download') { const path = this.getNodeParameter('path', i) as string; + const options = this.getNodeParameter('options', i); const binaryFile = await tmpFile({ prefix: 'n8n-sftp-' }); try { - await sftp!.get(path, createWriteStream(binaryFile.path)); + if (!options.enableConcurrentReads) { + await sftp!.get(path, createWriteStream(binaryFile.path)); + } else { + await sftp!.fastGet(path, binaryFile.path, { + concurrency: + options.maxConcurrentReads === undefined + ? 5 + : Number(options.maxConcurrentReads), + chunkSize: + (options.chunkSize === undefined ? 64 : Number(options.chunkSize)) * 1024, + }); + } const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i); const remoteFilePath = this.getNodeParameter('path', i) as string; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 629a625b9b..bb56df3a70 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -871,7 +871,7 @@ "@types/rfc2047": "^2.0.1", "@types/sanitize-html": "^2.11.0", "@types/showdown": "^1.9.4", - "@types/ssh2-sftp-client": "^5.1.0", + "@types/ssh2-sftp-client": "^9.0.5", "@types/uuid": "catalog:", "@types/xml2js": "catalog:", "eslint-plugin-n8n-nodes-base": "^1.16.3", @@ -943,7 +943,7 @@ "showdown": "2.1.0", "simple-git": "3.17.0", "snowflake-sdk": "2.1.0", - "ssh2-sftp-client": "7.2.3", + "ssh2-sftp-client": "12.0.1", "tmp-promise": "3.0.3", "ts-ics": "1.2.2", "uuid": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e00693ebb..11d50d6b3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2905,8 +2905,8 @@ importers: specifier: 2.1.0 version: 2.1.0(asn1.js@5.4.1)(encoding@0.1.13) ssh2-sftp-client: - specifier: 7.2.3 - version: 7.2.3 + specifier: 12.0.1 + version: 12.0.1 tmp-promise: specifier: 3.0.3 version: 3.0.3 @@ -2990,8 +2990,8 @@ importers: specifier: ^1.9.4 version: 1.9.4 '@types/ssh2-sftp-client': - specifier: ^5.1.0 - version: 5.3.2 + specifier: ^9.0.5 + version: 9.0.5 '@types/uuid': specifier: 'catalog:' version: 10.0.0 @@ -7389,8 +7389,8 @@ packages: '@types/sizzle@2.3.3': resolution: {integrity: sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==} - '@types/ssh2-sftp-client@5.3.2': - resolution: {integrity: sha512-s5R3hsnI3/7Ar57LG++gm2kxgONHtOZY2A3AgGzEwiJlHR8j7MRPDw1n/hG6oMnOUJ4zuoLNtDXgDfmmxV4lDA==} + '@types/ssh2-sftp-client@9.0.5': + resolution: {integrity: sha512-cpUO6okDusnfLw2hnmaBiomlSchIWNVcCdpywLRsg/h9Q1TTiUSrzhkn5sJeeyTM8h6xRbZEZZjgWtUXFDogHg==} '@types/ssh2-streams@0.1.12': resolution: {integrity: sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==} @@ -14617,14 +14617,18 @@ packages: ssh-remote-port-forward@1.0.4: resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} - ssh2-sftp-client@7.2.3: - resolution: {integrity: sha512-Bmq4Uewu3e0XOwu5bnPbiS5KRQYv+dff5H6+85V4GZrPrt0Fkt1nUH+uXanyAkoNxUpzjnAPEEoLdOaBO9c3xw==} - engines: {node: '>=10.24.1'} + ssh2-sftp-client@12.0.1: + resolution: {integrity: sha512-ICJ1L2PmBel2Q2ctbyxzTFZCPKSHYYD6s2TFZv7NXmZDrDNGk8lHBb/SK2WgXLMXNANH78qoumeJzxlWZqSqWg==} + engines: {node: '>=18.20.4'} ssh2@1.15.0: resolution: {integrity: sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==} engines: {node: '>=10.16.0'} + ssh2@1.16.0: + resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==} + engines: {node: '>=10.16.0'} + sshpk@1.18.0: resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} engines: {node: '>=0.10.0'} @@ -21952,7 +21956,7 @@ snapshots: '@types/sizzle@2.3.3': {} - '@types/ssh2-sftp-client@5.3.2': + '@types/ssh2-sftp-client@9.0.5': dependencies: '@types/ssh2': 1.11.6 @@ -24854,7 +24858,8 @@ snapshots: transitivePeerDependencies: - ts-toolbelt - err-code@2.0.3: {} + err-code@2.0.3: + optional: true error-ex@1.3.2: dependencies: @@ -29817,6 +29822,7 @@ snapshots: dependencies: err-code: 2.0.3 retry: 0.12.0 + optional: true promise@7.3.1: dependencies: @@ -31012,11 +31018,10 @@ snapshots: '@types/ssh2': 0.5.52 ssh2: 1.15.0 - ssh2-sftp-client@7.2.3: + ssh2-sftp-client@12.0.1: dependencies: concat-stream: 2.0.0 - promise-retry: 2.0.1 - ssh2: 1.15.0 + ssh2: 1.16.0 ssh2@1.15.0: dependencies: @@ -31026,6 +31031,14 @@ snapshots: cpu-features: 0.0.10 nan: 2.20.0 + ssh2@1.16.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.20.0 + sshpk@1.18.0: dependencies: asn1: 0.2.6