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

别等时光非礼了梦想. 提交于 2019-12-12 04:57:04

问题


I'm using loopback-component-storage with the filesystem provider to upload photos. It works great. But now I have a request to 'export/download' photos to a zip archive.

I've put together some code to add another method, downloadContainer() to Container from the file-storage example. It uses the Archiver module, everything seems to work fine, but the browser crashes after I call zip.finalize() I'm expecting to get a save file dialog box instead...

Here is my code so far:

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);
      });
    });
  }
}

and here is what I see in the console

    // 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

回答1:


This code expands on the suggestion from Bryan above and works with 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'
    }
  });



回答2:


I haven't done this before but I think you're on the right path from your comment.

Something like this should work:

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();
  });
});

Let me know how it goes!



来源:https://stackoverflow.com/questions/29137729/strongloop-loopback-storage-service-how-to-use-storageservice-downloadstream

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!