mafintosh / tar-stream

tar-stream is a streaming tar parser and generator.
MIT License
406 stars 92 forks source link

Pack and serve a glob directory as .tar.gz #76

Closed MadLittleMods closed 6 years ago

MadLittleMods commented 6 years ago

I had a bit of trouble figuring out how to serve a directory as a .tar.gz in an Express app. Here is a snippet on how I accomplished it.

As a gist, https://gist.github.com/MadLittleMods/7eedb4001c52acec104e91dbd80618b5

const Promise = require('bluebird');
const path = require('path');
const fs = require('fs-extra');
const stat = Promise.promisify(fs.stat);
const glob = Promise.promisify(require('glob'));
const tarstream = require('tar-stream');
const zlib = require('zlib');
const express = require('express');

function targzGlobStream(globString, options) {
  const stream = tarstream.pack();

  const addFileToStream = (filePath, size) => {
    return new Promise((resolve, reject) => {
      const entry = stream.entry({
        name: path.relative(options.base || '', filePath),
        size: size
      }, (err) => {
        if(err) reject(err);
        resolve();
      });

      fs.createReadStream(filePath)
        .pipe(entry);
    });
  };

  const getFileMap = glob(globString, Object.assign({ nodir: true }, options))
    .then((files) => {
      const fileMap = {};
      const stattingFilePromises = files.map((file) => {
        return stat(file)
          .then((fileStats) => {
            fileMap[file] = fileStats;
          });
      });

      return Promise.all(stattingFilePromises)
        .then(() => fileMap);
    });

  getFileMap.then((fileMap) => {
      // We can only add one file at a time
      return Object.keys(fileMap).reduce((promiseChain, file) => {
        return promiseChain.then(() => {
          return addFileToStream(file, fileMap[file].size);
        });
      }, Promise.resolve());
    })
    .then(() => {
      stream.finalize();
    });

  return stream.pipe(zlib.createGzip());
}

const app = express();

app.get('/logs.tar.gz', function (req, res) {
  const logDirPath = path.join(process.cwd(), './logs/');
  const tarGzStream = targzGlobStream(path.join(logDirPath, '**/*'), {
    base: logDirPath
  });

  res
    .set('Content-Type', 'application/gzip')
    .set('Content-Disposition', 'attachment; filename="logs.tar.gz"');

  tarGzStream.pipe(res);
});

Thanks to https://github.com/mafintosh/tar-stream/issues/64, https://github.com/mafintosh/tar-stream/issues/25

piranna commented 6 years ago

What's the intention of that? To work as an example?

MadLittleMods commented 6 years ago

@piranna Yep, just an example to find if you search issues or come in from Google.