-1

I'm creating a temporary JSON file in my NodeJS backend which holds the information the user has filled in a form. At the end of the form when user clicks on the download button, I run some Python script in NodeJS to validate the data and then create a temporary file of this JSON data and return it to user as a HTTP GET response.

Right now I'm using a timer to delete this temporary file after 10 seconds, which is bad. I want to know how to detect when the user has fully downloaded the file to their local disk from the browser so I can delete this temporary file in backend.

The client Angular code:

$scope.downloadForm = function() {
    var data = formDataFactory.getDataForSubmission();
    var url = '/FormSubmission/DownloadData';

    // Below POST call will invoke NodeJS to write the temporary file
    $http.post(url, data)
    .success(function(data, status) {
        $scope.downloadPath = data.filePath;
        $scope.downloadFile = data.fileName;
        url = '/tmp/forms/' + $scope.downloadFile;

        // If the temporary file writing is successful, then I get it with a GET method
        $http.get(url)
        .success(function(data, status) {
            $log.debug("Successfully got download data");
            $window.location = $scope.downloadPath;
        })
        .error(function(data, status) {
            $log.error("The get data FAILED");
        });

    })
    .error(function(data, status) {
        $log.error("The post data FAILED");
    });
}

$scope.download = function() {
    $scope.downloadForm();
    setTimeout(function() { //BAD idea
        $scope.deleteForm($scope.downloadPath);
    }, 10000);
}

The server NodeJS code:

// POST method for creating temporary JSON file
router.post('/FormSubmission/DownloadData', function(req, res) {
    if (!req.body) return res.sendStatus(400); // Failed to get data, return error
    var templateString = formTmpPath + 'form-XXXXXX.json';
    var tmpName = tmp.tmpNameSync({template: templateString});
    fs.writeFile(tmpName, JSON.stringify(req.body, null, 4), function(err) {
        if (err) {
            res.sendStatus(400);
        } else {
            res.json({ fileName: path.basename(tmpName), filePath: tmpName, out: ''});
        }
    });
});

// Get method for downloading the temporary form JSON file
router.get('/tmp/forms/:file', function(req, res) {
    var file = req.params.file;
    file = formTmpPath + file;
    res.download(file, downloadFileName, function(err) {
        if (err) debug("Failed to download file");
    });

});

Update: I'm trying to use a stream now to send the data back, but for some reason this get method is called twice!? Can't understand why!!

// Get method for downloading the temporary form JSON file
router.get('/tmp/forms/:file', function(req, res) {
    var filename = "ipMetaData.json";

    var file = req.params.file;
    file = formTmpPath + file;

    var mimetype = mime.lookup(file);
    const stats = fs.statSync(file);

    res.setHeader('Content-disposition', 'attachment; filename=' + filename);
    res.setHeader('Content-type', mimetype);
    res.setHeader('Content-Length', stats.size);

    console.log("Will send the download response for file: ", file);

    //var path = __dirname + "\\..\\tmp\\forms\\form-auSD9X.json";
    console.log("Creating read stream for path: " + file);
    var stream = fs.createReadStream(file);
    // This will wait until we know the readable stream is actually valid before piping
    stream.on('open', function () {
        // This just pipes the read stream to the response object (which goes to the client)
        stream.pipe(res);
    });
    // This catches any errors that happen while creating the readable stream (usually invalid names)
    stream.on('error', function(err) {
        console.log("Caught an error in stream"); console.log(err);
        res.end(err);
    });

    stream.on('end', () => {
        console.log("Finished streaming");
        res.end();
        //fs.unlink(file);
    });    
});
  • stream to the client and delete the file inside `.on('end', callback)` – Daniel Lizik Mar 01 '17 at 21:14
  • How do I stream to client? – nima_santur Mar 01 '17 at 21:28
  • Is this the correct way? fs.writeFileSync(tmpName, JSON.stringify(req.body, null, 4)); console.log("Setting header"); const stats = fs.statSync(tmpName); res.set({ 'Content-Type': 'aplication/json', 'Content-Length': stats.size }); console.log("Creating read stream"); fs.createReadStream(tmpName).pipe(res); – nima_santur Mar 01 '17 at 22:18

2 Answers2

0

if I understand your problem correctly, you can do this in different ways, but easiest way is first, remove the timer to remove the file, and remove it after the download completes from the backend as follows

router.get('/tmp/forms/:file', function(req, res) {
    var file = req.params.file;
    file = formTmpPath + file;
    res.download(file, downloadFileName, function(err) {
        if (err) debug("Failed to download file");
        else {
            // delete the file
            fs.unlink(file,function(err){
                if(err) debug(err);
            })
        }
    });

});
Abdullah Shahin
  • 1,002
  • 15
  • 19
  • I thought about that, but that's not working. I don't understand why, but the browser fails to download the file as it seems like the file doesn't exist when it attempts to grab it – nima_santur Mar 01 '17 at 21:09
  • that's another issue then, make sure the file path is correct, also, have a look at this post, maybe it will help as well http://stackoverflow.com/questions/20176982/res-download-not-working-in-my-case – Abdullah Shahin Mar 01 '17 at 21:14
  • I think you may have been right... for some reason my router.get method is being called twice!! When I look at the server log, it's being called twice every time. I don't know why. Client is only making the $http.get call once – nima_santur Mar 01 '17 at 23:57
0

The problem was with doing a get call and then change location to the file path which has the same path. I changed my API path and used the stream .on('end', callback) to remove the file.

    // If the temporary file writing is successful, then I get it with a GET method
    $http.get(url) --> this URL should be different from $window.location
    .success(function(data, status) {
        $log.debug("Successfully got download data");
        $window.location = $scope.downloadPath;
    })
    .err