Change the file name on the fly for downloading - node.js

I am saving user uploads by renaming it's original name to userID + '_' + new Date().getTime() + fileExt. I am storing the file properties in mongodb collections as :
{
name : String //generated name
, originalName : String //its original name
...
}
Now when the user requests for downloading of file I have to provide the file with its original name (which is persisted in db so no problem to grab it) to the user.
For the following request
GET /users/:userId/uploads/:fileId?type=download
I have this handler
//the mongoose query
UserUpload.findById(req.params.fileId).exec(function(err, doc){
var fileLocation = __dirname + '/public/uploads/users/' + req.params.userId + '/' + doc.name;
if(req.query.type && req.query.type == 'download') {
// I don't know what should I do now
// Downloader.download(fileLocation, newName);
}
});
I read wiki of node-static module but could not figure out how to do that job?

I found the answer here : Download a file from NodeJS Server using Express . Both using express and without using express.
It is too simple if you are using Express. Here is the documentation for res.download. I can't believe that the solution is just one line of code :
res.download('/path/to/file.ext', 'newname.ext');

Here what I use in one of my project, smil is a type of file I need to download, nevermind it.
In that project I have DOWNLOAD_DIR as global variable, which contain the full path to the download folder.
It may make a lot of people cringe (especcially the fileExistSync) but it s a start.
var DOWNLOAD_DIR = '/path/to/download',
url = require('url'),
http = require('http'),
/*
download
IN_:
file_url, url of the file to download
OUT:
file_name, full path to downloaded file, null if the file cound t be downloaded.
COM:
download a file, the file name will be the last part of the url (http://why.so/serious.txt => serious.txt).
WARNING: Do NOT follow redirections.
*/
function download(file_url, callback) {
var options = {host: url.parse(file_url).host, port: 80, path: url.parse(file_url).pathname},
file_name = url.parse(file_url).pathname.split('/').pop(),
//Creating the file
file = fs.createWriteStream(DOWNLOAD_DIR + file_name, {flags: 'w', encoding: 'binary'});
console.log('Downloading file from ' + file_url);
console.log('\tto ' + file_name);
http.get(options, function (res) {
res.pipe(file, {end: 'false'});
//When the file is complete
res.on('end', function () {
//Closing the file
file.end();
console.log('\t\tDownloaded '+ file_name);
callback(DOWNLOAD_DIR + file_name);
});
});
process.on('uncaughtException', function(err) {
console.log('Can t download ' + file_url + '\t(' + err + ')', false);
callback(null);
});
}
/*
download_smil
IN_:
file_url, url of the file to download
OUT:
file_name, full path to downloaded file
COM:
Follow http redirection and then call download function to download it.
You can modify the cad function to use custom names.
*/
function download_smil(file_url, callback) {
function cad(link, callback) {
//Does the file already exist?
var file = url.parse(link).pathname.substr(url.parse(link).pathname.lastIndexOf('/') + 1),
pkmn;
pkmn = fs.existsSync(DOWNLOAD_DIR + '/' + file);
if (pkmn) {
//YES: callback
console.log('File ' + file + ' already exist, skipping');
callback(DOWNLOAD_DIR + file, true);
} else {
//NO: Download it
console.log('Will download ' + link);
download(link, callback);
}
}
//GET the page
http.get(file_url, function (res) {
var link;
//console.log('res.statusCode = ' + res.statusCode + ' res.headers.location = ' + res.headers.location);
//Check if it is a redirect
if (res.statusCode > 300 && res.statusCode < 400 && res.headers.location) {
//console.log('redirect');
//Check if the hostname is in the location
if (url.parse(res.headers.location).hostname) {
//console.log('The link to the smil is res.headers.location');
link = res.headers.location;
cad(link, callback);
} else {
//console.log('The link to the smil is url_parse(file_url).hostname + res.headers.location = ' + url_parse(file_url).hostname + res.headers.location);
link = url_parse(file_url).hostname + res.headers.location;
cad(link, callback);
}
} else {
//console.log('The url is good : ' + file_url);
cad(file_url, callback);
}
});
}

Related

Canva publish extension API : Endpoint never get call

I try to make a Canva App with a publish extension.
I just follow the Quick start (https://docs.developer.canva.com/apps/extensions/publish-extensions/quick-start) with Glitch and it work well on it.
But when I try to put in on my own public host name, with a other port (like http://mydomaine.com:3000) Canva NEVER call my endpoint. I just write a log file of every action on my app post and I never get a update on it, and when I try the app on Canva.com it just show me a error message.
//Copy from the Quick Start
app.post('/publish/resources/upload', async (request, response) => {
try{
writeLog("Uploading file");
await fs.ensureDir(path.join(__dirname, 'export'));
// Get the first asset from the "assets" array
const [asset] = request.body.assets;
// Download the asset
const image = await jimp.read(asset.url);
const filePath = path.join(__dirname, 'export', asset.name);
await image.writeAsync(filePath);
// Respond with the URL of the published design
response.send({
type: 'SUCCESS',
url: url.format({
protocol: request.protocol,
host: request.get('host'),
pathname: asset.name,
}),
});
} catch (err) {
writeLog("ERROR (app.post('/publish/resources/upload'): " + err);
}
});
//Just log on the log file
function writeLog(log){
// fs.appendFile(path.join(__dirname, '/log/' + `${month}/${date}/${year}` +'log.txt'), dateDisplay + "|" + log + "\n", (err) => {
// if (err) throw err;
// });
var today = new Date();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
var dateTime = date + ' ' + time;
natifFS.appendFile('log.txt', dateTime + '| '+ log + "\n", (err) => {
if (err) throw err;
});
}
Last thing, when I try to call a post request on the same endpoint as Canva (/publish/resources/upload) with Postman, I get a update on my log.txt file
If anyone has idea, thank you.

Angular download file from byte array sent from node.js

I think I'm very close to what I want to do. I have the following api get method in node.js that is retrieving a file varbinary(MAX) from an SQL Server database. It was converted from a base64 encoded string before inserted so the Content Type information was stripped from the string.
node.js
router.get('/getFile', (req, res) => {
console.log("Calling getFile for file " + req.query.serialNumber + ".")
var serialNumber = req.query.serialNumber;
let request = new sql.Request(conn);
request.query('SELECT FileName + \'.\' + FileExtension AS \'File\', FileType, ContentType, SerialNumber, Chart ' +
'FROM dbo.ChangeFiles ' +
'WHERE SerialNumber = ' + serialNumber)
.then(function (recordset) {
log("Successfully retrieved file " + recordset[0].SerialNumber + " from database.");
log("Length of blob " + recordset[0].File + " is " + recordset[0].Chart.length)
res.status(200);
res.setHeader('Content-Type', recordset[0].ContentType);
res.setHeader('Content-Disposition', 'attachment;filename=' + recordset[0].File);
res.end(Buffer.from((recordset[0].Chart)));
}).catch(function (err) {
log(err);
res.status(500).send("Issue querying database!");
});
});
That works fine, but what to do in Angular to retrieve it and prompt for a download for the user has not been clear for me, nor has there been a lot as far as help/resources online. Here is what I have so far in my service class.
fileDownload.service.ts
downloadFile(serialNumber: string): Observable<any> {
return this.http.get(this.baseURL + '/getFile', { params: { serialNumber: serialNumber } })
.map(this.extractFile);
}
private extractFile(response: Response) {
const file = new Blob([response.blob]);
FileSaver.saveAs(file);
// const url = window.URL.createObjectURL(file);
// window.open(url);
return file;
}
As you can see I've tried a couple of approaches. The commented out portion of the extractFile method didn't work at all, and using the FileSaver.saveAs function produces a file download of an unknown type, so the headers sent from node.js didn't seem to affect the file itself.
Would someone be able to advise how to proceed in Angular with what is successfully being sent from node.js so that I can successfully download the file, regardless of type?
Thanks so much in advance.
I got it working afterall. I had to rework the api call so that it sent all of the file information separately so that the MIME type, and file name can be assigned to the file on the client side in the component class. For some reason when I tried to do so all in the api, it wouldn't work so that was my work around. So here is what works for me.
node.js api
router.get('/getFile', (req, res) => {
console.log("Calling getFile for file " + req.query.serialNumber + ".")
var serialNumber = req.query.serialNumber;
let request = new sql.Request(conn);
request.query('SELECT FileName + \'.\' + FileExtension AS \'File\', FileType, ContentType, SerialNumber, Chart ' +
'FROM dbo.ChangeFiles ' +
'WHERE SerialNumber = ' + serialNumber)
.then(function (recordset) {
log("Successfully retrieved file " + recordset[0].SerialNumber + " from database.");
log("Length of blob " + recordset[0].File + " is " + recordset[0].Chart.length)
res.send(recordset[0]);
}).catch(function (err) {
log(err);
res.status(500).send("Issue querying database!");
});
});
component class
downloadFile(serialNumber: string): void {
this.changeService.downloadFile(serialNumber).subscribe((res: any) => {
const ab = new ArrayBuffer(res.Chart.data.length);
const view = new Uint8Array(ab);
for (let i = 0; i < res.Chart.data.length; i++) {
view[i] = res.Chart.data[i];
}
const file = new Blob([ab], { type: res.ContentType });
FileSaver.saveAs(file, res.File);
console.log(res);
});
}
service class
downloadFile(serialNumber: string): Observable<any> {
return this.http.get(this.baseURL + '/getFile', { params: { serialNumber: serialNumber } })
.map(this.extractFile);
}
private extractFile(response: Response) {
// const file = new Blob([response.blob]);
// FileSaver.saveAs(file);
// const url = window.URL.createObjectURL(file);
// window.open(url);
const body = response.json();
return body || {};
}
Update your code to call subscribe instead of map

Error: PDFDocument: stream must have data

I am using pdf.js to parse pdf files. First I am uploading the file and then trying to parse that file.I am passing the url to parse function to read it in PDFJS.getDocument(url)
If I don't use the upload part and hardcode the URL of the pdf file it works.
Code :
var parse = function(payload,callback){
var data = payload;
if(data.file){
var name = data.file.hapi.filename;
var ext = name.split('.');
var extension = ext[1];
if(extension == "pdf"){
var path = __dirname + "/uploads/" + name;
checkFileExist();
var file = fs.createWriteStream(path);
file.on('error', function (err) {
console.error(err)
});
data.file.pipe(file);
var fileName = data.file.hapi.filename;
console.log(fileName);
var fileAbsolutepath = __dirname + "/uploads/" + fileName ;
console.log(fileAbsolutepath);
var parser = script.pdfParser(fileAbsolutepath,function(err,resp){
if(err){
callback(err);
}
else {
callback(resp);
}
})
callback(JSON.stringify(fileName));
}
else{
console.log("Invalid fileType");
callback(JSON.stringify("Invalid FileType"));
}
}
}
var checkFileExist = function() {
var path = __dirname + '/uploads';
fs.exists(path, function(exists) {
if (exists === false) fs.mkdirSync(path);
});
};
exports.pdfParser = function(url,callback){
PDFJS.workerSrc = 'pdf.worker.js';
PDFJS.getDocument(url).then(function (pdf) {
var pdfDocument = pdf;
}}
The file is uploaded properly with expected file size in uploads folder still I get error : "Error: PDFDocument: stream must have data"
To parse files with pdf.js, rather than loading the file with PDFJS.getDocument, try converting the pdf file to an arrayBuffer or Uint8Array, and use that to create a new LocalPdfManager object. Then you can call the LocalPdfManager's methods directly to parse the pdf.
Something like this:
import { LocalPdfManager } from 'pdfjs-dist/lib/core/pdf_manager';
pdfManager = new LocalPdfManager(1, arrayBuffer, '', {}, '');
// parameters = (docId, data, password, evaluatorOptions, docBaseUrl)
pdfManager.ensureDoc('parseStartXRef', []); // [] = arguments
pdfManager.ensureDoc('parse', false); // false = recoveryMode
pdfManager.ensureDoc('numPages');
pdfManager.ensureDoc('fingerprint');
Then look at pdfManager.pdfDocument for the parsed pdf data.
For example, the main "/Catalog" entry will be in pdfManager.pdfDocument.catalog.catDict.
I've used this method to successfully parse and modify pdf files locally in a browser. I haven't tried it on a server with node.js, but I expect it should work the same.

Modify image obtained from loopback-component-storage

I am using loopback for storing Image to the server.
I want to modify the file name of the file before getting saved to the server.
Also I want to convert it to another thumbnail form before getting saved.
Here is how I am doing.
At client side
Upload.upload(
{
url: '/api/containers/container_name/upload',
file: file,
fileName: "demoImage.jpg",
//Additional data with file
params:{
orderId: 1,
customerId: 1
}
});
At Server Side I am receiving the query "params" but not getting the "File Name"
My Storage model name is container
Container.beforeRemote('upload', function(ctx, modelInstance, next) {
//OUPTUTS: {orderId:1, customerId:1]}
console.log(ctx.req.query);
//Now I want to change the File Name of the file.
//But not getting how to do that
next();
})
How to change the File name of the File getting saved at the server?
I figured it out.
We have to define a custom function getFileName in boot/configure-storage.js.
Suppose my datasource for loopback-component-storage is presImage.
server/boot/configure-storage.js
module.exports = function(app) {
//Function for checking the file type..
app.dataSources.presImage.connector.getFilename = function(file, req, res) {
//First checking the file type..
var pattern = /^image\/.+$/;
var value = pattern.test(file.type);
if(value ){
var fileExtension = file.name.split('.').pop();
var container = file.container;
var time = new Date().getTime();
var query = req.query;
var customerId = query.customerId;
var orderId = query.orderId;
//Now preparing the file name..
//customerId_time_orderId.extension
var NewFileName = '' + customerId + '_' + time + '_' + orderId + '.' + fileExtension;
//And the file name will be saved as defined..
return NewFileName;
}
else{
throw "FileTypeError: Only File of Image type is accepted.";
}
};
}
common/models/container.js
Now suppose my container model is container.
module.exports = function(Container) {
Container.afterRemote('upload', function(ctx, modelInstance, next) {
var files = ctx.result.result.files.file;
for(var i=0; i<files.length; i++){
var ModifiedfileName = files[i].name;
console.log(ModifiedfileName) //outputs the modified file name.
} //for loop
next();
}); //afterRemote..
};
Now for converting it images to Thumbnail size
Download the quickthumb
Here is how to use it with loopback.
This code is copied directly from Loopback thumbnail view
common/models/container.js
module.exports = function(Container) {
var qt = require('quickthumb');
Container.afterRemote('upload', function(ctx, res, next) {
var file = res.result.files.file[0];
var file_path = "./server/storage/" + file.container + "/" + file.name;
var file_thumb_path = "./server/storage/" + file.container + "/thumb/" + file.name;
qt.convert({
src: file_path,
dst: file_thumb_path,
width: 100
}, function (err, path) {
});
next();
});
};
Piggybacking on the answer above, this configure-storage enables the file name to be set explicitly via req.params.filename and to default to the existing name if none is provided.
configure-storage.js
module.exports = function(app) {
//Function for checking the file type..
app.dataSources.storage.connector.getFilename = function(file, req, ignoreRes) {
if (!req.params.filename) {
return file.name
}
var fileExtension = file.name.split('.').pop()
return req.params.filename + '.' + fileExtension
};
}

NodeJS/Meteor how to download many files from external server through request.get and future?

I'm currently having a problem when trying to download many images through a external request.get, bellow are the code
var future = new Future();
var images, nome, blob;
.each(imoveis, function(dadosImovel, numeroImovel){
images = dadosImovel.images;
_.each(imagens, function(value, key){
// the name of the image to a permalink format, this function is working
nome = Meteor.createPermalinkFromString(value[3]);
// the link pointing to the image
blob = Meteor.getImage(value[0]);
Meteor.saveImage(blob, nome, '.jpeg');
});
});
// Get a image through a url
Meteor.getImage = function(url){
var options = {
url : url,
encoding : null
};
// get raw image
request.get(options, function(error, result, body) {
if (error) {
return console.error(error);
}
// pause until binaries are fully loaded
future['return'](body);
});
return future.wait()
};
// save a image in a server folder
Meteor.saveImage = function(name, blob, ext, encoding) {
var projectPath = basepath.resolve('.').split('.meteor')[0],
chroot = Meteor.chroot || projectPath + 'public', // (process.env['PWD'] +'/public') ;
path = chroot + (path ? '/' + path + '/' : '/'),
name = Meteor.cleanName(name || 'file'),
encoding= encoding || 'binary';
// TODO Add file existance checks, etc...
fs.writeFile(path + name + ext, blob, encoding, function(err) {
if (err) {
console.log(err);
throw (new Meteor.Error(500, 'Failed to save file.', err));
} else {
console.log('The file ' + name + ' (' + encoding + ') was saved to ' + path);
}
});
return true;
}
Now here what happens: in the first iteration of the loop I receive the image just fine, the problem appears for the next ones iterations.
If I have, 10 images all of then are saved with 10 different names (and they also have 10 links pointing to the correct images link) but when you visualize the image it's the first image on the list save for all the other 9, it's like once the first image is loaded meteor don't wait for the next binary/image codes to arrive before saving then.
Any thoughts on how to fix this?
Changing the
var Future = new Future();
from the outter scope to inside the function getImage fixed the issue with the images, but created another one.
My current code is
_.each(imoveis, function(dadosImovel, numeroImovel){
var imagens = dadosImovel.imagens;
_.each(imagens, function(value, key){
var nome = Meteor.createPermalinkFromString(value[3]);
var blob = Meteor.getImage(value[0]);
Meteor.saveImage(blob, nome, '.jpeg');
});
});
And now I have a infinite outer loop, saving again and again only the set of images from the first item of the loop.
For referente (in case this might help someone in the future) this is the code I used to make this works:
// imovel[0] = link, imovel[1] = width, imovel[2] = height, imovel[3] = title, imovel[4] = codigo
var futures = _.map(links, function(imovel) {
var future = new Future();
var onComplete = future.resolver();
var options = {
url : imovel[0],
encoding : null
};
// get raw image
request.get(options, function(error, result, body) {
if (error) {
return console.error(error);
}
var nome = Meteor.createPermalinkFromString(imovel[3]);
var data = {'codigo' : imovel[4], 'blob' : body, 'nome' : nome};
onComplete(error, data);
});
return future;
});
// wait for all futures to finish
Future.wait(futures);
// and grab the results out.
links = _.invoke(futures, 'get');

Resources