Home > other >  createWriteStream get file size when done
createWriteStream get file size when done

Time:01-19

I'm using createWriteStream to download videos files and when its done I want to get the size of the file it downloaded.

The reason for this is because if the file its attempting to download does not exist, my code still completes with success anyway and createWriteStream ends up writing a zero length file instead.

If I can determine the file size I can determine if its zero length (failed) or not (success).

Here's my code:

export const downloadTempFileFromUrl = async (downloadUrl, ext) => {

  return new Promise((resolve, reject) => {

    const uuid = uuidv4()
    const fileName = `${uuid}.${ext}`
    const filePath = path.resolve(os.tmpdir(), fileName)

    const writeStream = createWriteStream(filePath)

    const htt = (downloadUrl.startsWith('https')) ? https : http
    htt.get(downloadUrl, response => response.pipe(writeStream))

    writeStream.on('close', () => resolve({ success: true, fileName, filePath, uuid, downloadUrl, writeStream }))
    writeStream.on('error', () => reject({ success: false, fileName, filePath, uuid, downloadUrl, writeStream }))
  })
}

and the calling code is simply:

const response = await downloadTempFileFromUrl('http://server/path', 'mp4')
// how do I get the file size?
if (response.success) console.log(response.writeStream)

Thanks in advance

CodePudding user response:

To get the size of a file in Node.js, you can use the stat() method provided by the built-in fs module. This method works asynchronously and lists the statistics of a file at the given path.

All you need to do is pass in the path of the file and a callback function to the fs.stat() method. Once the file statistics are populated by Node.js, it will invoke the callback function with two arguments: an error message and the file stats:

const fs = require('fs');

// Read file stats
fs.stat('file.txt', (err, stats) => {
    if (err) {
        console.log(`File doesn't exist.`);
    } else {
        console.log(stats);
    }
});

The stats object returned by the fs.stat() function looks like the following:

Stats {
  dev: 16777221,
  mode: 33279,
  nlink: 1,
  uid: 501,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 5172976,
  size: 290,
  blocks: 8,
  atimeMs: 1606143471354.2327,
  mtimeMs: 1599218860000,
  ctimeMs: 1606074010041.4927,
  birthtimeMs: 1605423684000,
  atime: 2020-11-23T14:57:51.354Z,
  mtime: 2020-09-04T11:27:40.000Z,
  ctime: 2020-11-22T19:40:10.041Z,
  birthtime: 2020-11-15T07:01:24.000Z
}

To get the size of the file, you can use the size property in the stats object:

const size = stats.size; // 290 bytes

CodePudding user response:

You can try detecting if the stream was empty by listening to the data event and changing a flag if the event was fired. Like this:

let nonZero = false;
writeStream.on('data', () => nonZero = true);

And then resolve the promise using the nonZero flag:

resolve({ success: nonZero, ... });

So if the data event is fired at least once it means the stream is not empty and a file also is not empty.

CodePudding user response:

The answer I accepted is correct but a bit too generic for my liking. Here's an answer specific to createWriteStream. This puppy works.

The main changes from what I had earlier is using the finish event instead of the close event. I read that this event only fires on success whereas the close event fires also on an error. Not that it makes much difference.

Then when the finish event fires the code gets the file stats before resolving. Worth noting that Windows reports a zero length file with a small length - e.g. 68 bytes - so if detecting zero length files you need to allow for that.

export const downloadTempFileFromUrl = async (downloadUrl, ext) => {

  return new Promise((resolve, reject) => {

    const uuid = uuidv4()
    const fileName = `${uuid}.${ext}`
    const filePath = path.resolve(os.tmpdir(), fileName)

    const writeStream = createWriteStream(filePath)

    const htt = (downloadUrl.startsWith('https')) ? https : http
    htt.get(downloadUrl, response => response.pipe(writeStream))

    writeStream.on('finish', () => {
      const VERY_SMALL_FILE_SIZE = 1000 // on Windows zero length files are reported to have a very small size - e.g. 68 bytes
      const statsResponse = appStatSync(filePath)
      const success = statsResponse.success && statsResponse.stats.size > VERY_SMALL_FILE_SIZE
      resolve({ success, fileName, filePath, uuid, downloadUrl, writeStream, stats: statsResponse.stats })
    })
    writeStream.on('error', () => reject({ success: false, fileName, filePath, uuid, downloadUrl, writeStream, stats: null }))
  })
}

appStatSync is just a simple wrapper as follows:

import { statSync } from 'fs'
import { logger } from './logger.js'

export const appStatSync = filePath => {
  try {
    const stats = statSync(filePath)
    return { success: true, stats, err: null }
  } catch (err) {
    logger.error('statSync error')
    logger.error(err)
    return { success: false, stats: null, err }
  }
}
  •  Tags:  
  • Related