コード例 #1
0
ファイル: logger.js プロジェクト: ejeklint/couch-daemon
module.exports = function(url, options) {
  options = options || {};
  options.name = options.name || pkg.name;
  options.info = options.info || function(msg) {
    console.info('[' + new Date + '] [info] Daemon "' + options.name + '" :: ' + msg);
  };
  options.error = options.error || function(msg) {
    console.error('[' + new Date + '] [error] Daemon "' + options.name + '" :: ' + msg);
  };
  options.debug = options.debug || function(msg) {
    console.log('[' + new Date + '] [debug] Daemon "' + options.name + '" :: ' + msg);
  };

  
  return _.through(function(source) {
    return source
      .on('data', function(data) {
        if (data && data.type === 'log') {
          var level = data.level || 'info';

          if (typeof options[level] !== 'function') {
            options.error('Cannot log with level ' + level);
            return;
          }

          options[level](data.message);
        }
      })
      .on('error', function(data) {
        options.error(JSON.stringify(data));
      });
  });
};
コード例 #2
0
ファイル: masseur.js プロジェクト: jo/massage-couch
module.exports = function masseur(url, options) {
  options = options || {};

  var couch = nano(url);
  var config = {};

  return _.pipeline(
    // gather configs
    _.map(function(data) {
      if (data && data.stream === 'compile') {
        Object.keys(data.doc).forEach(function(key) {
          config[data.id + '/' + key] = data.doc[key];
        });
      }
      return data;
    }),

    // process docs
    _.through(function(source) {
      return _(function(push, done) {
        source.on('data', function(data) {
          if (data && data.stream === 'changes' && data.doc && data.db_name) {
            async.eachSeries(Object.keys(config), function(key, next) {
              try {
                config[key](data.doc, couch.use(data.db_name), next);
              } catch(e) {
                push(null, {
                  type: 'log',
                  message: 'Error running ' + key + ': ' + e
                });
              }
            }, function(err, res) {
              push(null, {
                db_name: data.db_name,
                seq: data.seq,
                type: 'log',
                message: 'Complete: ' + data.db_name + '/' + data.doc._id
              });
            });
          }
        });
        source.on('error', push);
        source.on('end', done);
      });
    })
  );
};
コード例 #3
0
module.exports = function couchmagick(url, options) {
  options = options || {};
  options.concurrency = options.concurrency || 1;
  options.timeout = options.timeout || 60 * 1000; // 1 minute


  var couch = nano(url);
  var configs = {};


  // convert attachment
  // TODO: process
  // * configs
  // * attachments
  // * versions
  // in one go. Don't do the through split pipeline stuff.
  function convertAttachment(data, callback) {
    var db = couch.use(data.db_name);

    // get target doc
    db.get(data.target.id, function(err, doc) {
      data.target.doc = doc || { _id: data.target.id };
      data.target.doc.couchmagick = data.target.doc.couchmagick || {};
      data.target.doc.couchmagick[data.target.id] = data.target.doc.couchmagick[data.target.id] || {};

      
      // do not process attachments twice, respect revpos
      if (data.target.doc.couchmagick[data.target.id][data.target.name] && data.target.doc.couchmagick[data.target.id][data.target.name].revpos === data.source.revpos) {
        return callback(null, data);
      }


      // insert couchmagick stamp
      data.target.doc.couchmagick[data.target.id][data.target.name] = {
        id: data.source.id,
        name: data.source.name,
        revpos: data.source.revpos
      };


      // query params, doc_name is used by nano as id
      var params = {
        doc_name: data.target.id
      };
      if (data.target.doc._rev) {
        params.rev = data.target.doc._rev;
      }
      
      // attachment multipart part
      var attachment = {
        name: data.target.name,
        content_type: data.target.content_type,
        data: []
      };

      // convert process
      var c = spawn('convert', data.args);

      // collect errors
      var cerror = [];
      c.stderr.on('data', function(err) {
        cerror.push(err);
      });

      // convert timeout
      var kill = setTimeout(function() {
        cerror.push(new Buffer('timeout'));
        // send SIGTERM
        c.kill();
      }, options.timeout);

      // collect output
      c.stdout.on('data', function(data) {
        attachment.data.push(data);
      });

      // concat output
      c.stdout.on('end', function() {
        clearTimeout(kill);
        attachment.data = Buffer.concat(attachment.data);
      });

      // convert finish
      c.on('close', function(code) {
        // store exit code
        data.code = code;
        data.target.doc.couchmagick[data.target.id][data.target.name].code = data.code;

        if (code === 0) {
          // no error: make multipart request
          return db.multipart.insert(data.target.doc, [attachment], params, function(err, response) {
            if (err) {
              return callback(err);
            }

            data.response = response;
            if (response.rev) {
              data.target.rev = response.rev;
            }

            callback(null, data);
          });
        }
      
        // store error
        data.error = Buffer.concat(cerror).toString();
        data.target.doc.couchmagick[data.target.id][data.target.name].error = data.error;

        // store document stup, discard attachment
        db.insert(data.target.doc, data.target.id, function(err, response) {
          if (err) {
            return callback(err);
          }

          data.response = response;
          if (response.rev) {
            data.target.rev = response.rev;
          }

          callback(null, data);
        });
      });


      // request attachment and pipe it into convert process
      db.attachment.get(data.source.id, data.source.name).pipe(c.stdin);
    });
  }


  // processing queue
  var convert = async.queue(convertAttachment, options.concurrency);


  return _.pipeline(
    // gather configs
    _.map(function(data) {
      if (data.stream === 'compile') {
        var cfg = {};
        cfg[data.id] = data.doc;

        _.extend(cfg, configs);
      }
      return data;
    }),

    // Decide whether a whole doc needs processing at all
    _.filter(function(data) {
      if (!data.doc) {
        return false;
      }
      if (!data.doc._attachments) {
        return false;
      }

      if (!Object.keys(data.doc._attachments).length) {
        return false;
      }

      return true;
    }),

    // split stream into each config
    // TODO: this prevents us from supporting multiple attachments per document
    // and therefore needs serialisation
    _.map(function(data) {
      return Object.keys(configs).map(function(config) {
        return {
          db_name: data.db_name,
          seq: data.seq,
          doc: data.doc,
          config: configs[config]
        };
      });
    }),
    _.flatten(),

    // Decide if couchmagick should be run on a specific attachment
    _.filter(function(data) {
      if (typeof data.config.filter === 'function' && !data.config.filter(data.doc)) {
        return false;
      }
 
      return true;
    }),

    // split stream into attachments
    // TODO: this prevents us from supporting multiple attachments per document
    // and therefore needs serialisation
    _.map(function(data) {
      return Object.keys(data.doc._attachments).map(function(name) {
        return {
          db_name: data.db_name,
          seq: data.seq,
          doc: data.doc,
          config: data.config,
          name: name
        };
      });
    }),
    _.flatten(),

    // filter attachments with builtin
    _.filter(function(data) {
      if (!data.doc) {
        return false;
      }
      if (!data.name) {
        return false;
      }

      return data.doc._attachments[data.name].content_type.match(/^image\//);
    }),

    // split stream into versions
    // TODO: this prevents us from supporting multiple attachments per document
    // and therefore needs serialisation
    _.map(function(data) {
      return Object.keys(data.config.versions).map(function(key) {
        var version = data.config.versions[key];

        // version defaults
        version.id = version.id || '{id}/{version}';
        version.name = version.name || '{basename}-{version}{extname}';
        version.content_type = version.content_type || 'image/jpeg';
        version.args = version.args || [];

        // first arg is input pipe
        if (!version.args[0] || version.args[0] !== '-') {
          version.args.unshift('-');
        }
        // last arg is output pipe
        if (version.args.length < 2 || !version.args[version.args.length - 1].match(/^[a-z]{0,3}:-$/)) {
          version.args.push('jpg:-');
        }

        // run version filter
        if (typeof version.filter === 'function' && !version.filter(data.doc, data.name)) {
          return;
        }

        // construct target doc
        var id = strformat(version.id, {
          id: data.doc._id,
          docuri: docuri.parse(data.doc._id),
          parts: data.doc._id.split('/'),
          version: key
        });
        var name = strformat(version.name, {
          id: data.doc._id,
          docuri: docuri.parse(data.doc._id),
          parts: data.doc._id.split('/'),
          version: key,

          name: data.name,
          extname: path.extname(data.name),
          basename: path.basename(data.name, path.extname(data.name)),
          dirname: path.dirname(data.name)
        });


        return {
          db_name: data.db_name,
          seq: data.seq,
          source: {
            id: data.doc._id,
            name: data.name,
            revpos: data.doc._attachments[data.name].revpos,
            couchmagick: data.doc.couchmagick
          },
          args: version.args,
          target: {
            id: id,
            name: name,
            content_type: version.content_type
          }
        };
      });
    }),
    _.flatten(),


    // filter derived versions to prevent cascades
    // eg:
    //   single-attachment/thumbnail
    //   single-attachment/thumbnail/thumbnail
    //   single-attachment/thumbnail/thumbnail/thumbnail
    _.filter(function(data) {
      var derivative = data.source.couchmagick &&
        data.source.couchmagick[data.source.id] &&
        data.source.couchmagick[data.source.id][data.source.name] &&
        data.source.couchmagick[data.source.id][data.source.name].id;

      return !derivative;
    }),


    // process attachments
    _.through(function(source) {
      return _(function(push, done) {
        source
          .on('data', function(data) {
            convert.push(data, function(err, res) {
              push(err, res);
            });
          })
          .on('error', push)
          .on('end', done);
      });
    }),

    _.map(function(data) {
      if (!data.response) {
        return data;
      }

      return {
        db_name: data.db_name,
        seq: data.seq,
        type: 'log',
        message: 'Complete: ' + JSON.stringify(data.response)
      };
    })
  );
};
コード例 #4
0
ファイル: compile.js プロジェクト: ejeklint/couch-daemon
module.exports = function(url, options) {
  options = options || {};
  options.name = options.name || pkg.name;

  var context = {
    log: options.info || console.log
  };

  function compile(str, id) {
    var source = '(' + str + ')';
    var name = id + '.js';
    var script = vm.createScript(source, name);

    return  script.runInNewContext(context);
  }

  function compileObj(obj, id) {
    if (typeof obj === 'string' && obj.match(IS_FUNCTION)) {
      return compile(obj, id);
    }

    if (typeof obj === 'object') {
      Object.keys(obj).forEach(function(key) {
        var name = [id, key].join('/');

        obj[key] = compileObj(obj[key], name);
      });
    }

    return obj;
  }

  function compileDoc(data, done) {
    var error = null;
    var config = {
      stream: 'compile',
      db_name: data.db_name,
      id: data.id
    };
    var doc = data.doc[options.name];

    try {
      config.doc = compileObj(doc, data.id);
    } catch(e) {
      error = { error: 'compile_error', reason: e };
    }

    done(error, error ? null : config);
  }


  return _.through(function(source) {
    var sourceEnded = false;

    var target = _(function(push, done) {
      source
        .on('data', function(data) {
          push(null, data);

          if (data && data.db_name && data.id && data.id.match(/^_design\//) && data.doc && data.doc[options.name]) {
            compileDoc(_.extend({}, data), function(err, config) {
              push(err, config);

              if (sourceEnded) {
                push(null, _.nil);
              }
            });
          }
        })
        .on('error', push);
    });

    source.on('end', function() {
      sourceEnded = true;
    });

    return target;
  });
};