Strongloop 环回存储服务:如何使用 StorageService.downloadStream() 下载容器中所有照片的 ZIP 文件?

Strongloop loopback-storage-service: how to use StorageService.downloadStream() to download a ZIP of all photos in container?

我正在使用 loopback-component-storage 和文件系统提供商来上传照片。它很好用。但现在我请求将 'export/download' 张照片压缩成一个 zip 存档。

我整理了一些代码来添加另一种方法,从文件存储示例中 downloadContainer()Container。它使用 Archiver 模块,一切似乎都工作正常,但浏览器在我调用 zip.finalize() 后崩溃 我希望得到一个 save file 对话框...

到目前为止,这是我的代码:

Container.downloadContainer = function(container, files, res, cb) {
  var DELIM, _appendFile, _finalize, _oneComplete, _remaining, filenames, storageService, zip;
  zip = Archiver('zip');
  zip.pipe(res);
  storageService = this;
  _remaining = {};
  _appendFile = function(zip, container, filename) {
    var reader;
    console.log('appendFile=' + filename);
    reader = storageService.downloadStream(container, filename, function(stream) {
      return console.log('storageService.downloadStream() resp=', _.keys(stream));
    });
    zip.append(reader, {
      name: filename
    });
  };
  zip.on('error', function(err) {
    console.log('zip entry error', err);
    res.status(500).send({
      error: err.message
    });
  });
  zip.on('entry', function(o) {
    return _oneComplete(o.name);
  });
  _oneComplete = function(filename) {
    delete _remaining[filename];
    console.log('_oneComplete(): ', {
      remaining: _.keys(_remaining),
      size: zip.pointer()
    });
    if (_.isEmpty(_remaining)) {
      _finalize();
    }
  };
  _finalize = function() {
    console.log('calling zip.finalize() ...');
    res.on('close', function() {
      console.log('response closed');
      res.attachment(container + '.zip');
      return res.status(200).send('OK').end();
    });
    zip.finalize();
  };
  if (files === 'all' || _.isEmpty(files)) {
    console.log('files=', files);
    storageService.getFiles(container, function(err, ssFiles) {
      _remaining = _.object(_.pluck(ssFiles, 'name'));
      console.log('filenames=', _.keys(_remaining));
      return ssFiles.forEach(function(file) {
        _appendFile(zip, container, file.name);
      });
    });
  }
}

这是我在控制台中看到的内容

    // log
    files= all
    filenames= [ 'IMG_0799.PNG', 'IMG_0800.PNG', 'IMG_0801.PNG', 'IMG_0804.PNG' ]
    appendFile=IMG_0799.PNG
    appendFile=IMG_0800.PNG
    appendFile=IMG_0801.PNG
    appendFile=IMG_0804.PNG
    _oneComplete():  { remaining: [ 'IMG_0800.PNG', 'IMG_0801.PNG', 'IMG_0804.PNG' ],
      size: 336110 }
    _oneComplete():  { remaining: [ 'IMG_0801.PNG', 'IMG_0804.PNG' ], size: 460875 }
    _oneComplete():  { remaining: [ 'IMG_0804.PNG' ], size: 1506464 }
    _oneComplete():  { remaining: [], size: 1577608 }
    calling zip.finalize() ...
    // then browser crash

我以前没有这样做过,但我认为你的评论是正确的。

像这样的东西应该可以工作:

var AWS = app.dataSources.amazon;
var container = 'c1';

app.get('/export/download', function (req, res) {
  var zip = Archiver('zip');
  // create the Archiver and pipe it to our client response.
  zip.pipe(res);
  // ask AWS for all the files in the container
  AWS.getFiles(container, function (err, files) {
    files.forEach(function (file) {
      // append each file stream to the zip archive
      zip.append(AWS.download({
        container: container,
        remote: file
      }), { name : file });
    });
    // I think finalize should end the stream and notify the client
    zip.finalize();
  });
});

让我知道进展如何!

此代码扩展了上述 Bryan 的建议并适用于 provider=filesystem

Archiver = require('archiver')
module.exports = function(Container) {
  Container.downloadContainer = function(container, files, res, cb) {
    var DELIM, _appendFile, _finalize, _oneComplete, _remaining, filenames, storageService, zip, zipFilename;
    zip = Archiver('zip');
    zipFilename = container + '.zip';
    storageService = this;
    _remaining = {};
    _appendFile = function(zip, container, filename) {
      var reader;
      reader = storageService.downloadStream(container, filename);
      zip.append(reader, {
        name: filename
      });
      console.log("appending", {
        name: filename
      });
    };
    res.on('close', function() {
      console.log('Archive wrote %d bytes', zip.pointer());
      return res.status(200).send('OK').end();
    });
    res.attachment(zipFilename);
    zip.pipe(res);
    zip.on('error', function(err) {
      console.log('zip entry error', err);
      res.status(500).send({
        error: err.message
      });
    });
    zip.on('entry', function(o) {
      return _oneComplete(o.name);
    });
    _oneComplete = function(filename) {
      delete _remaining[filename];
      console.log('_oneComplete(): ', {
        remaining: _.keys(_remaining),
        size: zip.pointer()
      });
      if (_.isEmpty(_remaining)) {
        _finalize();
      }
    };
    _finalize = function() {
      console.log('calling zip.finalize() ...');
      zip.finalize();
    };
    if (files === 'all' || _.isEmpty(files)) {
      console.log('downloadContainer, files=', files);
      storageService.getFiles(container, function(err, ssFiles) {
        _remaining = _.object(_.pluck(ssFiles, 'name'));
        return ssFiles.forEach(function(file) {
          _appendFile(zip, container, file.name);
        });
      });
    } else {
      DELIM = ',';
      filenames = files.split(DELIM);
      _remaining = _.object(filenames);
      console.log('filenames=', _.keys(_remaining));
      _.each(filenames, function(filename) {
        _appendFile(zip, container, filename);
      });
    }
  };
  Container.remoteMethod('downloadContainer', {
    shared: true,
    accepts: [
      {
        arg: 'container',
        type: 'string',
        'http': {
          source: 'path'
        }
      }, {
        arg: 'files',
        type: 'string',
        required: false,
        'http': {
          source: 'path'
        }
      }, {
        arg: 'res',
        type: 'object',
        'http': {
          source: 'res'
        }
      }
    ],
    returns: [],
    http: {
      verb: 'get',
      path: '/:container/downloadContainer/:files'
    }
  });