6

I have a method to download file from FTP server and it works fine on smaller files, but when I use it to download file of ~5GB size of zip type, it downloads it, but after that it doesn't do anything. When it reaches 100% of downloading, then script doesn't continue. Should I wait if it's actually doing something in the background after download is complete? Is there filesize limit?

const FTP = require('ftp')

which can be found on npm

downloadFile: params => {
    return new Promise((resolve, reject) => {
      let ftpClient = new FTP()
      let total = params.state.fileSize
      let progress = 0
      ftpClient.on('ready', _ => {
        console.log(`Downloading ${params.targetedFile} ...`);
        ftpClient.get(params.targetedFile, (err, stream) => {
          if (err) reject(err)
          stream.on('data', buffer => {
            progress += buffer.length
            process.stdout.write(`Progress: ${(progress/total*100).toFixed(2)}% (${progress}/${total})  \r`)
          })
          stream.once('close', _ => {
            ftpClient.end()
            console.log(`Saved downloaded file to ${params.localDir}`);
            resolve(params.localDir)
          })
          stream.pipe(fs.createWriteStream(params.localDir))
        })
      })
      ftpClient.connect(params.auth)
    })
  }

Basically, the callback for stream.once('close', ...) doesn't get executed when large file is downloaded. And it gets executed for smaller file of same type.

Kunok
  • 8,089
  • 8
  • 48
  • 89

2 Answers2

0

I recommend You to handle event of closing write stream.

Reason is simple: we read from ftp's read stream and pipe to write stream, everything is ok, when file is successfully closed.

So code:

downloadFile: params => {
    return new Promise((resolve, reject) => {
      let ftpClient = new FTP()
      let total = params.state.fileSize
      let progress = 0
      ftpClient.on('ready', _ => {
        console.log(`Downloading ${params.targetedFile} ...`);
        ftpClient.get(params.targetedFile, (err, stream) => {
          if (err) {
            ftpClient.end();
            return reject(err);
          }

          stream.on('data', buffer => {
            progress += buffer.length
            process.stdout.write(`Progress: ${(progress/total*100).toFixed(2)}% (${progress}/${total})  \r`)
          });

          // opening writeStream to file
          let finished = false;
          const writeStream = fs.createWriteStream(params.localDir);

          writeStream.on('finish', (result) => { // handling finish
            finished = true;
            ftpClient.end();
            console.log(`Finish triggered ${params.localDir}`);
            console.log(result);
            resolve(params.localDir);
          });

          writeStream.on('close', (result) => { // handling close
            ftpClient.end();
            console.log(`Close triggered ${params.localDir}`);
            console.log(result);
            resolve(params.localDir);
          })

          // piping readStream to writeStream
          stream.pipe(writeStream);
        })
      })
      ftpClient.connect(params.auth)
    })
  }
num8er
  • 18,604
  • 3
  • 43
  • 57
  • How is this code different than the one I posted except you assigned `fs.createWriteStream(params.localDir)` to const `writeStream` ? – Kunok Apr 26 '17 at 14:10
  • updated my answer, You should read about read and write streams and `nodejs stream end vs close` – num8er Apr 26 '17 at 14:13
  • 1
    Oh I see difference now. I modified code, ran it successfully on small file. Now I am going to test it on big file, I'll reply with result. – Kunok Apr 26 '17 at 14:18
  • I ran it on big file. And the issue remains. It's still stuck at the same point. My last log is `Progress: 100.00% (4618223308/4618223308) ` and expected log within `writeStream.once('close', ...)` is not executed and script doesn't still move forwards. – Kunok Apr 26 '17 at 15:49
  • what about `on('close'` (not `once`) ? – num8er Apr 26 '17 at 15:50
  • I can try this one. I will reply once I finish running it. It takes around 1h to complete download. – Kunok Apr 26 '17 at 15:52
  • can I ask You to check my latest answer, in fact there are 2 events: `close` and `finish` so I want You to run that procedure and check the output, and write here what happened – num8er Apr 26 '17 at 15:57
  • I ran latest changes on small file. Both events have `result` variable as `undefined`. Both were triggered. I am also running this code on large file now in order to see if it will execute either callback. – Kunok Apr 26 '17 at 16:05
  • That's correct, now check with big file. I guess it has errors – num8er Apr 26 '17 at 16:10
  • Running it on large file now, I also want to mention that I have no problem with large file when downloaded by hand using Filezilla. – Kunok Apr 26 '17 at 16:11
  • After running it on the big file, results are same as before. Nothing new was logged, it's still stuck. – Kunok Apr 26 '17 at 16:42
  • But file downloaded successfuly? – num8er Apr 26 '17 at 16:43
  • It may show progress of reading, but how about progress of writing? Maybe it's still writing? – num8er Apr 26 '17 at 16:44
  • Yes file is downloaded successfully. It's size matches size on the server. It might be the case that it requires some time due to size. I will keep it on for a while. Other than that I might add case in `data` event listener, when it's 100% to resolve and continue with other functionalities within script. When I inspect that process in System Monitor app, it uses CPU 0%, and Status is changing from `Sleeping` to `Running` and back constantly. – Kunok Apr 26 '17 at 16:46
  • You may handle by this way: If it's 100% so have `fs.stats()` to check size of file with source size. – num8er Apr 26 '17 at 16:52
  • Ok this workaround seems to work. My script finally completed this part and continued to the next one. Will you add that part to your answer or should I post another one? – Kunok Apr 26 '17 at 19:12
  • Better post answer Yourself. That's Your solution. (; – num8er Apr 26 '17 at 19:32
0

This code might give you idea how to handle this in a bit hacky way.

Basically this method allows you to download file from FTP server and save it to local filesystem. It outputs current progress complete_percentage% (current/total) in a single line. Upon finish, it resolves promise, returns path to local file, the same one that you passed as param.

/**
   * @name downloadFile
   * @desc downloads file from FTP server
   * @param  params, Object of params
   *   @prop auth: object, or null, authorization params
   *   @prop targetedFile: {String} filename e.g. data.txt
   *   @prop localDir: {String} filename on local disk
   *   @prop state: {Object} fileinfo object, {Int} .fileSize property is required
   * @return Promise, resolves given localDir
   */
  downloadFile: params => {
    return new Promise((resolve, reject) => {
      // validate param types
      if(typeof params.auth !== 'object'
      || typeof params.targetedFile !== 'string'
      || typeof params.localDir !== 'string'
      || typeof params.state !== 'object'
      || typeof params.state.fileSize !== 'number'
      ) throw new Error('You are either missing properties or passed wrong types')

      // initialize
      let ftpClient = new FTP()
      let total = params.state.fileSize
      let progress = 0

      //
      ftpClient.on('ready', _ => {
        console.log(`Downloading ${params.targetedFile} ...`)
        // get file
        ftpClient.get(params.targetedFile, (err, stream) => {
          if (err){
            ftpClient.end()
            return reject(err)
          }

          // upon data receive
          stream.on('data', buffer => {
            progress += buffer.length
            // if progress is complete
            if(progress === total){
              // start checking if local filesize matches server filesize
              let interval = setInterval(_ => {
                if(fs.statSync(params.localDir).size === total){
                  console.log(`Downloading file complete. Location: ${params.localDir}`);
                  clearInterval(interval)
                  ftpClient.end()
                  resolve(params.localDir)
                }
              })
            }
            // show current progress in percentages and bytes
            process.stdout.write(`Progress: ${(progress/total*100).toFixed(2)}% (${progress}/${total})  \r`)
          })
          // pipe writestream to filesystem to write these bytes
          stream.pipe(fs.createWriteStream(params.localDir))
        })
      })
      ftpClient.connect(params.auth)
    })//promise
  }
Kunok
  • 8,089
  • 8
  • 48
  • 89