nodejs download files from sftp - node.js

I need to download some .txt.pgp files from sftp. I've tried npm ssh2, ssh2-sftp-client and node-ssh without any success.
The closest I got so far is the list of the files in the remote folder using sftp.readdir (ssh2) or sftp.list (ssh2-sftp-client).
I've tried pipe and fs.createWriteStream and sftp.fastGet but there's no file saved on my local machine.
const conn = new Client();
conn.on('ready', () => {
console.log('Client :: ready');
conn.sftp((err, sftp) => {
if (err) throw err;
sftp.readdir('out', (err, list) => {
if (err) throw err;
list.forEach(item => {
console.log(item.filename);
const fileName = item.filename;
sftp.fastGet(fileName, fileName, {}, downloadError => {
if(downloadError) throw downloadError;
console.log("Succesfully uploaded");
});
})
conn.end();
});
});
}).connect(config);
OR
const Client = require('ssh2-sftp-client');
const sftp = new Client();
sftp.connect(config).then(() => {
return sftp.list('out');
})
.then(files => {
// console.log(files);
if (files.length > 0) {
console.log('got list of files!');
}
files.map(file => {
const fileName = file.name;
sftp.get(fileName)
.then(() => {
fs.writeFile(fileName);
});
})
})
.then(() => {
sftp.end();
}).catch((err) => {
console.log(err);
});

Regarding your first attempt (with the ssh2 module), there are three issues that I can see:
You are calling conn.end() outside of a series of preceding async functions, which is almost definitely causing the SSH session to close before you've finished downloading the files.
You are not providing the sftp.fastGet() function with the correct path to the remote file. Earlier in the code, you call sftp.readdir() with the remote directory argument 'out', which returns a list of files relative to the remote directory. (Point is: you need to prepend the remote directory to the file name to create a correctly qualified path.)
You're not handling the stream event error, so I suspect you're not getting useful error messages to help troubleshoot.
Try something like:
const Client = require('ssh2').Client;
const conn = new Client();
const sshOpt = someFunctionThatPopulatesSshOptions();
const remoteDir = 'out';
conn.on('ready', () => {
conn.sftp((err, sftp) => {
if (err) throw err;
sftp.readdir(remoteDir, (err, list) => {
if (err) throw err;
let count = list.length;
list.forEach(item => {
let remoteFile = remoteDir + '/' + item.filename;
let localFile = '/tmp/' + item.filename;
console.log('Downloading ' + remoteFile);
sftp.fastGet(remoteFile, localFile, (err) => {
if (err) throw err;
console.log('Downloaded to ' + localFile);
count--;
if (count <= 0) {
conn.end();
}
});
});
});
});
});
conn.on('error', (err) => {
console.error('SSH connection stream problem');
throw err;
});
conn.connect(sshOpt);
This should address all the issues I mentioned. Specifically:
We are using a count variable to ensure the SSH session is closed only after all files are downloaded. (I know it is not pretty.)
We are prepending remoteDir to all of our remote file downloads.
We're listening for the error event in our conn stream.

Related

How to make express download link with GridFsBucket?

As the title says, how do you make a direct download link with a file from mongoDB(GridFsBucket) using express?
The file should be downloadable from memory, as i dont want to save it temporarily on the server.
I have this method:
async function downloadFileFromDB(fileId) {
var gridfsbucket = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
chunkSizeBytes: 1024,
bucketName: 'filesBucket'
});
try {
const stream = gridfsbucket.openDownloadStream(fileId)
const fileBuffer = Buffer.from(stream)
return fileBuffer
} catch (err) {
stream.on('error', () => {
console.log("Some error occurred in download:" + error);
})
console.log(err);
}
}
And this route:
router.get('/download-file', async (req,res) => {
const fileId = req.query.fileId
const ObjectFileId = new ObjectId(fileId)
const fileBuffer = await fileFacade.downloadFileFromDB(ObjectFileId)
res.download(fileBuffer)
})
But res.download wants a path and not a buffer. Aswell im not sure i can make a buffer directly from the openDownloadStream method.
Can anyone help?
I believe you need to write the data to your res object. I accomplished this like:
const readStream = gridfs.openDownloadStreamByName(filename);
readStream.on("data", (chunk) => {
res.write(chunk);
});
readStream.on("end", () => {
res.status(200).end();
mongoClient.close();
});
readStream.on("error", (err) => {
console.log(err);
res.status(500).send(err);
});
So, you may just have to do:
res.write(fileBuffer).end();
//// Instead of doing:
// res.download(fileBuffer);

discord.js store argument data in a file with the name + guild id

Alright, so basically, I am using File System in order to grab the user input, create a file with the inputted name, and store it in a specific folder. However, I am unable to get the guild.id and combine it with the name of the file the user made.
Basically, what I want it to do is when you run the createchar (name) command, it creates a file with the name plus the guild id, so basically createchar test would result in a .txt file being created named testguildID. This is so that the command can work on many different servers.
This also applies to editchar as well.
However, when I run the command, the command doesn't work, nor do I get an error in the console.
Here's the code:
client.on("message", message => {
if(message.author.bot) return;
const args = message.content.slice(config.prefix.length).trim().split(' ');
const command = args.shift().toLowerCase();
if(command === "createchar") {
fs.writeFile(`./characters/${args + guild.id}.txt`, 'utf8', function (err) {
if (err) throw err;
message.reply('Character created successfully.');
console.log('File created successfully.');
});
}
if(command === "editchar") {
fs.readFileSync(`./characters/${args[0] + guild.id}.txt`, 'utf8');
var data = new Uint8Array(Buffer.from(`${args}`));
fs.writeFile(`./characters/${args[0] + guild.id}.txt`, data, (err) => {
if (err) throw err;;
message.reply('Character information edited successfully');
console.log('File edited successfully');
})
}
if(command === "char") {
var char = fs.readFileSync(`./characters/${args[0]}.txt`, 'utf8');
var char3 = char.replace(`${args[0]}`, '');
message.channel.send(">>>" + char3.replace(/,/g, " "));
}
});
You forgot to add message. to guild.id, so thats the problem :)
client.on("message", message => {
if(message.author.bot) return;
const args = message.content.slice(config.prefix.length).trim().split(' ');
const command = args.shift().toLowerCase();
if(command === "createchar") {
fs.writeFile(`./characters/${args + message.guild.id}.txt`, 'utf8', function (err) {
if (err) throw err;
message.reply('Character created successfully.');
console.log('File created successfully.');
});
}
if(command === "editchar") {
fs.readFileSync(`./characters/${args[0] + message.guild.id}.txt`, 'utf8');
var data = new Uint8Array(Buffer.from(`${args}`));
fs.writeFile(`./characters/${args[0] + message.guild.id}.txt`, data, (err) => {
if (err) throw err;;
message.reply('Character information edited successfully');
console.log('File edited successfully');
})
}
if(command === "char") {
var char = fs.readFileSync(`./characters/${args[0]}.txt`, 'utf8');
var char3 = char.replace(`${args[0]}`, '');
message.channel.send(">>>" + char3.replace(/,/g, " "));
}
});

How do I download directory with all files and folders inside using npm module ssh2 for nodejs?

I am trying to download files and folders via SFTP to my local machine.I am using the below code and am able to download the files in a particular directory but am unable to download the folders(with their respective files and folders recursively) in the same directory
const folderDir=moment().format('MMM YYYY/D');
const remoteDir = '/var/www/html/view';
const remoteDir = 'Backup';
const download=(remoteDir,folderDir,FolderName)=>{
conn.on('ready', function() {
conn.sftp((err, sftp) => {
if (err) throw err;
sftp.readdir(remoteDir, (err, list) => {
if (err) throw err;
let count = list.length;
list.forEach(item => {
let remoteFile = remoteDir + '/' + item.filename;
var localFile = 'C:/Users/Desktop/'+folderDir+'/'+FolderName+'/' + item.filename;
//console.log('Downloading ' + remoteFile);
sftp.fastGet(remoteFile, localFile, (err) => {
if (err) throw err;
//console.log('Downloaded to ' + localFile);
count--;
if (count <= 0) {
conn.end();
}
});
});
});
});
}).connect({
host: '0.0.0.0',
port: 0,
username: 'test',
privateKey: require('fs').readFileSync('')
});
}
Install the module by running:
npm i ssh2-sftp-client
You can do it like this:
let fs = require('fs');
let Client = require('ssh2-sftp-client');
let sftp = new Client();
sftp.connect({
host: '',
port: '22',
username: 'your-username',
password: 'your-password'
}).then(() => {
// will return an array of objects with information about all files in the remote folder
return sftp.list('/');
}).then(async (data) => {
// data is the array of objects
len = data.length;
// x is one element of the array
await data.forEach(x => {
let remoteFilePath = '/' + x.name;
sftp.get(remoteFilePath).then((stream) => {
// save to local folder ftp
let file = './ftp/' + x.name;
fs.writeFile(file, stream, (err) => {
if (err) console.log(err);
});
});
});
}).catch((err) => {
console.log(err, 'catch error');
});
For more info on the ssh2-sftp-client module.

Node.js tcp connection

How to send "End" message after resolving promise? Sometimes I can send 2 "end" messages out of 4, sometimes 3. Files from FTP are being downloaded and it's ok. The only thing that doesn't work is sending "end" message after downloading a file. Do you have any idea why this code doesn't work properly?
This code was updated:
const ftp = require("jsftp");
const fs = require("fs");
const net = require("net");
const mkdirp = require("mkdirp");
class ftpCredentials {
constructor(host) {
this.user = "xxx";
this.pass = "xxx";
this.host = host;
}
}
const downloadFromFTP = (credentials, file) => {
const client = new ftpCredentials(credentials.host);
const ftpClient = new ftp(client);
return new Promise((res, rej) => {
let buf = null;
ftpClient.get(file, (err, stream) => {
if (!err && typeof stream !== "undefined") {
// Events
stream.on("data", (data) => {
if (buf === null) buf = new Buffer(data);
else buf = Buffer.concat([buf, data]);
});
stream.on("close", (err) => {
if (err) rej("FILE_ERROR");
const actualPath = `${credentials.path}/${file}`;
fs.writeFile(actualPath, buf, "binary", (err) => {
if (err) rej(err);
ftpClient.raw("quit", (err, data) => {
if (err) rej(err)
res(file);
});
});
});
// Resume stream
stream.resume();
} else {
rej("STREAM_ERROR");
}
});
})
}
const handleSavingFile = (credentials, filesOnFTP) => {
mkdirp(credentials.path, () => {
fs.readdir(credentials.path, (err, fileNames) => {
if (err) return err;
const needToConnectToFTP = filesOnFTP.filter(name => fileNames.indexOf(name) !== -1).length === 0;
const socketForEndMsg = net.createConnection(18005, credentials.host, () => {
Promise.all(filesOnFTP.map((file) => {
return new Promise((resolve, reject) => {
// The problem is here:
const socketWrite = socketForEndMsg.write(`End|ftp://${credentials.host}/${file}`, "UTF16LE");
resolve(socketWrite);
// Events
socketForEndMsg.on("error", () => {
console.log("Problem with sending End message!");
reject();
});
});
})).then(() => {
socketForEndMsg.end();
}).catch((err) => {
console.log(err);
});
});
})
})
}
const getScheme = (credentials) => {
const socketForData = net.createConnection(18005, credentials.host, () => socketForData.write("Scheme", "UTF16LE"));
// Events
socketForData.on("close", () => console.log("TCP Connection closed"));
socketForData.on("error", err => console.log(err));
socketForData.on("data", (data) => {
socketForData.end();
const toUTF16Format = Buffer.from(data).toString("UTF16LE");
const arrayFromTCPMessage = toUTF16Format.split(/\||;/);
const filteredImages = arrayFromTCPMessage.filter(item => item.startsWith("scheme"))
const isOK = arrayFromTCPMessage[0] === "OK";
if (isOK) {
handleSavingFile(credentials, filteredImages);
}
})
}
module.exports = getScheme;
Error message: Error: This socket is closed
at Socket._writeGeneric (net.js:722:18)
at Socket._write (net.js:782:8)
at doWrite (_stream_writable.js:407:12)
at writeOrBuffer (_stream_writable.js:393:5)
at Socket.Writable.write (_stream_writable.js:290:11)
at Promise (xxx\getScheme.js:56:29)
at new Promise (<anonymous>)
at Promise.all.filesOnFTP.map (xxx\getScheme.js:54:18)
at Array.map (<anonymous>)
at Socket.net.createConnection (xxx\getScheme.js:52:32)
I see that, you like to listen to error event & that made you to use Promise to catch the error. But, the placement of the error event handler registration is wrong, as it is inside .map function call. So, error event will be registered number of times of filesOnFTP length.
I've moved that error handler to next line & using writable flag to see, if the socket is still writable before writing to it. I have also added few more event handlers, which will give you more information about the socket status(for debugging, you can remove them later).
const handleSavingFile = (credentials, filesOnFTP) => {
mkdirp(credentials.path, () => {
fs.readdir(credentials.path, (err, fileNames) => {
if (err) return err;
const needToConnectToFTP = filesOnFTP.filter(name => fileNames.indexOf(name) !== -1).length === 0;
const socketForEndMsg = net.createConnection(18005, credentials.host, () => {
for(let file of filesOnFTP) {
// Before write to socket, check if it is writable still!
if(socketForEndMsg.writable) {
socketForEndMsg.write(`End|ftp://${credentials.host}/${file}`, "UTF16LE");
}
else {
console.log('Socket is not writable! May be closed already?');
}
}
});
// This is the correct place for the error handler!
socketForEndMsg.on("error", (error) => {
console.log("Problem with sending End message!", error);
});
socketForEndMsg.on("close", () => {
console.log("Socket is fully closed!");
});
socketForEndMsg.on("end", () => {
console.log("The other end of the socket has sent FIN packet!");
});
});
});
}
Let me know if this works!
Thanks!
You can try to wait for connection event on socketForEndMsg and then start sending your data
const handleSavingFile = (credentials, filesOnFTP) => {
mkdirp(credentials.path, () => {
fs.readdir(credentials.path, (err, fileNames) => {
if (err) return err;
const needToConnectToFTP = filesOnFTP.filter(name => fileNames.indexOf(name) !== -1).length === 0;
const socketForEndMsg = net.createConnection(18005, credentials.host);
socketForEndMsg.on('connect', () => {
Promise.all(filesOnFTP.map((file) => {
return new Promise((resolve, reject) => {
// The problem is here:
const socketWrite = socketForEndMsg.write(`End|ftp://${credentials.host}/${file}`, "UTF16LE");
resolve(socketWrite);
// Events
socketForEndMsg.on("error", () => {
console.log("Problem with sending End message!");
reject();
});
});
})).then(() => {
socketForEndMsg.end();
}).catch((err) => {
console.log(err);
});
})
})
})
}

Bluebird promise, promise.each only executes once

There is a function called musicPromise(). What this function does is
It gets all mp4 files and loop through it.
then it tries to convert each mp4 to mp3, using fluent-ffmpeg
The problem I am facing is
It only converts 1 file, no matter how many mp4 files I have.
And it seems never reach to proc.on('end', (x) => {
Full code here:
// search
const glob = require('glob');
// wait for
const Promise = require('bluebird');
// fs
const fs = require('fs');
// mp3
const ffmpeg = require('fluent-ffmpeg');
// video source file path
const videoPath = '/home/kenpeter/Videos/4K\ Video\ Downloader';
// audio source file path
const audioPath = __dirname + "/audio";
// child process, exec
const exec = require('child_process').exec;
// now rename promise
function renamePromise() { return new Promise((resolve, reject) => {
glob(videoPath + "/**/*.mp4", (er, files) => {
Promise.each(files, (singleClipFile) => {
return new Promise((resolve1, reject1) => {
let arr = singleClipFile.split("/");
let lastElement = arr[arr.length - 1];
let tmpFileName = lastElement.replace(/[&\/\\#,+()$~%'":*?<>{}\ ]/g, "_");
let tmpFullFile = videoPath + "/"+ tmpFileName;
// rename it
fs.rename(singleClipFile, tmpFullFile, function(err) {
if ( err ) console.log('ERROR: ' + err);
console.log("-- Rename one file --");
console.log(tmpFullFile);
resolve1();
}); // end rename
});
})
.then(() => {
console.log('--- rename all files done ---');
resolve();
});
});
}); // end promise
};
// music promise
function musicPromise() { new Promise((resolve, reject) => {
glob(videoPath + "/**/*.mp4", (er, files) => {
Promise.each(files, (singleClipFile) => {
return new Promise((resolve1, reject1) => {
// test
console.log('-- music promise --');
console.log(singleClipFile);
// split
let arr = singleClipFile.split("/");
// e.g. xxxx.mp4
let clipFile = arr[arr.length - 1];
// e.g. xxxx no mp4
let fileName = clipFile.replace(/\.[^/.]+$/, "");
// music file name
let musicFile = fileName + '.mp3';
// set source
let proc = new ffmpeg({source: singleClipFile});
// set ffmpeg path
proc.setFfmpegPath('/usr/bin/ffmpeg');
// save mp3
proc.output("./audio/" + musicFile);
// proc on error
proc.on('error', (err) => {
console.log(err);
});
// done mp3 conversion
proc.on('end', (x) => {
console.log("single mp3 done!");
console.log(x);
// it is resolve1..............
resolve1();
});
// Run !!!!!!!!!!!!!
proc.run();
});
})
.then(() => {
console.log('--------- all mp3 conversion done --------');
resolve();
});
}); // end glob
});
};
// adb kill
function adbKillPromise() { return new Promise((resolve, reject) => {
exec("adb kill-server", (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
console.log('---adb kill---');
resolve();
});
});
};
// adb start
function adbStartPromise() { return new Promise((resolve, reject) => {
exec("adb start-server", (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
console.log('---adb start---');
resolve();
});
});
};
// adb push promise
function adbPushPromise() { return new Promise((resolve, reject) => {
glob(audioPath + "/**/*.mp3", (er, files) => {
Promise.each(files, (singleMusicFile) => {
return new Promise((resolve1, reject1) => {
let cmd = "adb push" + " " + singleMusicFile + " " + "/sdcard/Music";
exec(cmd, (err, stdout, stderr) => {
console.log(cmd);
resolve1();
});
});
})
.then(() => {
console.log('---- done push all music ---');
resolve();
});
});
});
};
// Run !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
renamePromise()
.then(musicPromise)
.then(adbKillPromise)
.then(adbStartPromise)
.then(adbPushPromise)
.then(() => {
console.log('---- all done----');
process.exit(0);
})
.catch(err => {
console.log('Error', err);
process.exit(1);
});
A very stupid mistake
function musicPromise() { new Promise((resolve, reject) => {
should be
function musicPromise() { return new Promise((resolve, reject) => {

Resources