<commit_before>var fs = require('fs');
var queue = require('queue-async');
var request = require('request');
var Path = require('path');
var debug = require('debug');
debug.enable('webkitgtk:stall');
debug.enable('webkitgtk:error');
var WebKit = require('webkitgtk');
debug = debug('express-dom');

var Dom = module.exports = function(model, opts) {
	if (!Dom.pool) {
		Dom.pool = new Pool(Dom.settings.max);
		Dom.handlers = {}; // hash to store each view <-> middleware handler
	}
	var h = Dom.handlers[model];
	if (h) return h.chainable;
	var h = new Handler(model, opts);
	Dom.handlers[model] = h;
	return h.chainable;
};

Dom.Handler = Handler;

Dom.settings = {
	max: 16,
	display: process.env.DISPLAY || 0,
	style: fs.readFileSync(__dirname + '/index.css'),
	debug: !!process.env.INSPECTOR,
	console: !!process.env.DEBUG,
	images: false,
	private: true,
	stall: 15000,
	allow: "same-origin"
};

Dom.plugins = require('./plugins');

Dom.authors = {
	before: [],
	current: [],
	after: []
};

Dom.users = {
	before: [Dom.plugins.nostylesheets],
	current: [],
	after: []
};

Dom.author = function(mw, position) {
	var list = Dom.authors;
	if (!position) position = 'current';
	if (!list[position]) throw new Error("Unknown position for express-dom author middleware", position);
	list[position].push(mw);
	return Dom;
};

Dom.use = function(mw, position) {
	var list = Dom.users;
	if (!position) position = 'current';
	if (!list[position]) throw new Error("Unknown position for express-dom user middleware", position);
	list[position].push(mw);
	return Dom;
};

function Handler(model, opts) {
	debug("new handler", model, opts);
	if (isRemote(model)) {
		this.url = model;
	} else {
		this.model = model; // app.get('statics') is available in middleware
	}
	this.opts = opts || {};
	this.chainable = this.middleware.bind(this);
	this.chainable.author = this.author.bind(this);
	this.chainable.use = this.use.bind(this);
	this.authors = {
		before: Dom.authors.before.slice(),
		current: Dom.authors.current.slice(),
		after: Dom.authors.after.slice()
	};
	this.users = {
		before: Dom.users.before.slice(),
		current: Dom.users.current.slice(),
		after: Dom.users.after.slice()
	};
	if (this.init) this.init(); // used by raja
}

Handler.prototype.author = function(mw, position) {
	var list = this.authors;
	if (!position) position = 'current';
	if (!list[position]) throw new Error("Unknown position for express-dom author middleware", position);
	list[position].push(mw);
	return this.chainable;
};

Handler.prototype.use = function(mw, position) {
	var list = this.users;
	if (!position) position = 'current';
	if (!list[position]) throw new Error("Unknown position for express-dom user middleware", position);
	list[position].push(mw);
	return this.chainable;
};

Handler.prototype.middleware = function(req, res, next) {
	var h = this;
	if (h.model != null && h.url == null) {
		var path = h.model;
		var root = req.app.get('statics');
		if (!root) return next(new Error("Cannot find view, undefined 'statics' application setting"));
		if (!path) path = "index";
		var path = Path.resolve(root, path);
		if (path.indexOf(root) !== 0) return next(new Error("Path outside statics dir\n" + path));
		if (path.slice(-1) == "/") path += "index";
		if (Path.extname(path) != ".html") path += ".html";
		h.url = path;
	}
	var url = req.protocol + '://' + req.headers.host + req.url;
	if (url == h.url) {
		return next(new Error("The view has the same url as the requested page"));
	}
	h.getView(h.url, req, res, function(err, view) {
		if (err) return next(err);
		debug('view loaded', view.key || view.url);
		h.getPage(view, url, req, res, function(err, user) {
			if (err) return next(err);
			debug('page built', user.key || user.url);
			h.finish(user, res);
		});
	});
};

Handler.prototype.getPage = function(view, url, req, res, cb) {
	var h = this;
	h.getAuthored(view, url, req, res, function(err, author) {
		if (err) return cb(err);
		debug('page authored', author.key || author.url);
		h.getUsed(author, url, req, res, cb);
	});
};

Handler.prototype.get = function(url, depend, req, cb) {
	// this is useful for raja dom proxy
	cb = cb || req || depend;
	cb(null, new SimpleResource(url));
};

Handler.prototype.finish = function(user, res) {
	for (var name in user.headers) {
		if (name in {'Content-Type':1, 'Content-Encoding':1}) {
			res.set(name, user.headers[name]);
		}
	}
	if (user.mtime) res.set('Last-Modified', user.mtime.toUTCString());
	res.send(user.data);
	debug('page sent', user.url, user.headers);
};

Handler.prototype.getView = function(url, req, res, cb) {
	var h = this;
	h.get(url, function(err, resource) {
		if (err) return cb(err);
		if (resource.valid) {
			debug("got valid view", resource.key || resource.url);
			return cb(null, resource);
		}
		var loader = isRemote(url) ? h.loadRemote : h.loadLocal;
		loader.call(h, url, function(err, body) {
			if (!err || body) {
				resource.data = body;
				resource.valid = true;
				resource.mtime = new Date();
				resource.save(cb);
			}	else {
				if (!err) err = new Error("Empty initial html in " + url);
				if (!err.code || err.code == 'ENOENT') err.code = 404;
				else err.code = 500;
				cb(err);
			}
		});
	});
};

Handler.prototype.loadLocal = fs.readFile;
Handler.prototype.loadRemote = function(url, cb) {
	request(url, function(err, res, body) {
		cb(err, body);
	});
};

Handler.prototype.getAuthored = function(view, url, req, res, cb) {
	var h = this;
	h.get(url, view, {headers: {
		'X-Author': view.key || view.url,
		'Vary': 'X-Author'
	}}, function(err, resource) {
		if (err) return cb(err);
		if (resource.valid) {
			debug("got valid authored html", resource.key || resource.url);
			return cb(null, resource);
		}
		resource.headers['Content-Type'] = 'text/html';
		if (h.authors.before.length || h.authors.current.length || h.authors.after.length) {
			Dom.pool.acquire(function(err, page) {
				if (err) return cb(err);
				debug('view.data length', view.data.length);
				var opts = {
					content: view.data,
					console: true
				};
				if (!Dom.settings.debug) opts.style = Dom.settings.style;
				debug('author preload', url);
				page.preload(url, opts);
				h.processMw(page, resource, h.authors, req, res);
				page.wait('idle').html(function(err, html) {
					debug('author.data length', html && html.length);
					page.locked = false;
					Dom.pool.release(page, function(perr) {
						if (err) return cb(err);
						resource.data = html;
						resource.valid = true;
						resource.mtime = new Date();
						resource.save(cb);
					});
				});
			});
		} else {
			debug("no author plugins");
			resource.data = view.data;
			resource.valid = true;
			resource.mtime = new Date();
			resource.save(cb);
		}
	});
};

Handler.prototype.getUsed = function(author, url, req, res, cb) {
	var h = this;
	h.get(url, author, req, function(err, resource) {
		if (err) return cb(err);
		if (resource.valid) {
			debug("got valid user html", resource.key || resource.url);
			return cb(null, resource);
		}
		if (author.mtime > resource.mtime) {
			debug('author is more recent than user, reload page', resource.url || resource.key);
			delete resource.page;
		}
		Dom.pool.acquire(resource.page, function(err, page) {
			if (err) return cb(err);
			if (!resource.page) {
				resource.page = page;
				var opts = {};
				var customFn;
				for (var key in h.opts) {
					if (key == 'params' && typeof h.opts[key] == 'function') customFn = h.opts[key];
					else opts[key] = h.opts[key];
				}
				if (customFn) customFn(opts, req);
				if (!opts.content) {
					debug('use author data length', author.data.length);
					opts.content = author.data;
				} else {
					debug('use content from customFn data length', opts.content.length);
				}
				for (var k in Dom.settings) if (Dom.settings.hasOwnProperty(k) && opts[k] === undefined) {
					opts[k] = Dom.settings[k];
				}
				debug("user load", resource.key || resource.url, "with stall", opts.stall);
				page.load(resource.url, opts);
				h.processMw(page, resource, h.users, req, res);
			}
			next();
		});
		function next(err) {
			if (err) return cb(err);
			resource.mtime = new Date();
			resource.headers['Content-Type'] = 'text/html';
			var page = resource.page;
			if (!page) return cb(new Error("resource.page is missing for\n" + resource.key));
			page.wait('idle');
			resource.output(page, function(err, str) {
				Dom.pool.unlock(page, function(resource) {
					// breaks the link when the page is recycled
					debug("unlocked page removed from resource", resource.key || resource.url);
					delete resource.page;
				}.bind(this, resource));
				if (err) return cb(err);
				debug('got html', resource.key || resource.url);
				resource.data = str;
				resource.valid = true;
				resource.save(cb);
			});
		}
	});
};

Handler.prototype.processMw = function(page, resource, mwObj, req, res) {
	var list = mwObj.before.concat(mwObj.current).concat(mwObj.after);
	for (var i=0; i < list.length; i++) {
		list[i](page, resource, req, res);
	}
};

function Pool(cacheSize) {
	this.list = [];
	this.max = cacheSize;
	this.extra = 0;
	this.count = 0;
	this.queue = [];
}

Pool.prototype.acquire = function(page, cb) {
	if (page && !cb) {
		cb = page;
		page = null;
	}
	if (page) {
		page.locked = true;
	} else if (this.count < this.max + this.extra) {
		this.count++;
		page = WebKit(Dom.settings);
		page.locked = true;
		this.list.push(page);
	} else {
		for (var i=0; i < this.list.length; i++) {
			page = this.list[i];
			if (!page.locked) {
				page.locked = true;
				if (typeof page.unlock == "function") {
					debug("acquire call page.unlock");
					page.unlock();
					page.removeAllListeners();
					delete page.unlock;
				}
				break;
			}
			page = null;
		}
		if (!page) {
			console.info("Need one extra instance", this.extra++);
			setImmediate(this.process.bind(this));
		}
	}
	if (page) {
		if (this.extra) {
			this.extra--;
			if (this.extra == 0) console.info("No more extra instances, total", this.list.length);
		}
		cb(null, page);
	} else {
		this.queue.push(cb);
	}
};

Pool.prototype.unlock = function(page, unlockCb) {
	debug('unlock called', unlockCb ? 'with' : 'without', 'callback');
	page.unlock = unlockCb;
	page.locked = false;
	setImmediate(this.process.bind(this));
};

Pool.prototype.release = function(page, cb) {
	if (page.locked) return setImmediate(cb);
	page.locked = true; // make sure page cannot be acquired while it is unloaded
	page.unload(function(err) {
		if (page.unlock) {
			debug("release call page.unlock");
			page.unlock();
			delete page.unlock;
		}
		page.removeAllListeners();
		page.locked = false;
		if (cb) cb();
		setImmediate(this.process.bind(this));
	}.bind(this));
};

Pool.prototype.process = function() {
	var next = this.queue.shift();
	if (next) this.acquire(next);
};

function SimpleResource(url) {
	this.url = url;
	this.headers = {};
}
SimpleResource.prototype.save = function(cb) {
	cb(null, this);
};
SimpleResource.prototype.output = function(page, cb) {
	page.html(cb);
};

function isRemote(url) {
	return /^https?:\/\//.test(url);
}

<commit_msg>debug disable DEBUG parsing when enable is used<commit_after>var fs = require('fs');
var queue = require('queue-async');
var request = require('request');
var Path = require('path');
var debug = require('debug')('express-dom');
var WebKit = require('webkitgtk');

var Dom = module.exports = function(model, opts) {
	if (!Dom.pool) {
		Dom.pool = new Pool(Dom.settings.max);
		Dom.handlers = {}; // hash to store each view <-> middleware handler
	}
	var h = Dom.handlers[model];
	if (h) return h.chainable;
	var h = new Handler(model, opts);
	Dom.handlers[model] = h;
	return h.chainable;
};

Dom.Handler = Handler;

Dom.settings = {
	max: 16,
	display: process.env.DISPLAY || 0,
	style: fs.readFileSync(__dirname + '/index.css'),
	debug: !!process.env.INSPECTOR,
	console: !!process.env.DEBUG,
	images: false,
	private: true,
	stall: 15000,
	allow: "same-origin"
};

Dom.plugins = require('./plugins');

Dom.authors = {
	before: [],
	current: [],
	after: []
};

Dom.users = {
	before: [Dom.plugins.nostylesheets],
	current: [],
	after: []
};

Dom.author = function(mw, position) {
	var list = Dom.authors;
	if (!position) position = 'current';
	if (!list[position]) throw new Error("Unknown position for express-dom author middleware", position);
	list[position].push(mw);
	return Dom;
};

Dom.use = function(mw, position) {
	var list = Dom.users;
	if (!position) position = 'current';
	if (!list[position]) throw new Error("Unknown position for express-dom user middleware", position);
	list[position].push(mw);
	return Dom;
};

function Handler(model, opts) {
	debug("new handler", model, opts);
	if (isRemote(model)) {
		this.url = model;
	} else {
		this.model = model; // app.get('statics') is available in middleware
	}
	this.opts = opts || {};
	this.chainable = this.middleware.bind(this);
	this.chainable.author = this.author.bind(this);
	this.chainable.use = this.use.bind(this);
	this.authors = {
		before: Dom.authors.before.slice(),
		current: Dom.authors.current.slice(),
		after: Dom.authors.after.slice()
	};
	this.users = {
		before: Dom.users.before.slice(),
		current: Dom.users.current.slice(),
		after: Dom.users.after.slice()
	};
	if (this.init) this.init(); // used by raja
}

Handler.prototype.author = function(mw, position) {
	var list = this.authors;
	if (!position) position = 'current';
	if (!list[position]) throw new Error("Unknown position for express-dom author middleware", position);
	list[position].push(mw);
	return this.chainable;
};

Handler.prototype.use = function(mw, position) {
	var list = this.users;
	if (!position) position = 'current';
	if (!list[position]) throw new Error("Unknown position for express-dom user middleware", position);
	list[position].push(mw);
	return this.chainable;
};

Handler.prototype.middleware = function(req, res, next) {
	var h = this;
	if (h.model != null && h.url == null) {
		var path = h.model;
		var root = req.app.get('statics');
		if (!root) return next(new Error("Cannot find view, undefined 'statics' application setting"));
		if (!path) path = "index";
		var path = Path.resolve(root, path);
		if (path.indexOf(root) !== 0) return next(new Error("Path outside statics dir\n" + path));
		if (path.slice(-1) == "/") path += "index";
		if (Path.extname(path) != ".html") path += ".html";
		h.url = path;
	}
	var url = req.protocol + '://' + req.headers.host + req.url;
	if (url == h.url) {
		return next(new Error("The view has the same url as the requested page"));
	}
	h.getView(h.url, req, res, function(err, view) {
		if (err) return next(err);
		debug('view loaded', view.key || view.url);
		h.getPage(view, url, req, res, function(err, user) {
			if (err) return next(err);
			debug('page built', user.key || user.url);
			h.finish(user, res);
		});
	});
};

Handler.prototype.getPage = function(view, url, req, res, cb) {
	var h = this;
	h.getAuthored(view, url, req, res, function(err, author) {
		if (err) return cb(err);
		debug('page authored', author.key || author.url);
		h.getUsed(author, url, req, res, cb);
	});
};

Handler.prototype.get = function(url, depend, req, cb) {
	// this is useful for raja dom proxy
	cb = cb || req || depend;
	cb(null, new SimpleResource(url));
};

Handler.prototype.finish = function(user, res) {
	for (var name in user.headers) {
		if (name in {'Content-Type':1, 'Content-Encoding':1}) {
			res.set(name, user.headers[name]);
		}
	}
	if (user.mtime) res.set('Last-Modified', user.mtime.toUTCString());
	res.send(user.data);
	debug('page sent', user.url, user.headers);
};

Handler.prototype.getView = function(url, req, res, cb) {
	var h = this;
	h.get(url, function(err, resource) {
		if (err) return cb(err);
		if (resource.valid) {
			debug("got valid view", resource.key || resource.url);
			return cb(null, resource);
		}
		var loader = isRemote(url) ? h.loadRemote : h.loadLocal;
		loader.call(h, url, function(err, body) {
			if (!err || body) {
				resource.data = body;
				resource.valid = true;
				resource.mtime = new Date();
				resource.save(cb);
			}	else {
				if (!err) err = new Error("Empty initial html in " + url);
				if (!err.code || err.code == 'ENOENT') err.code = 404;
				else err.code = 500;
				cb(err);
			}
		});
	});
};

Handler.prototype.loadLocal = fs.readFile;
Handler.prototype.loadRemote = function(url, cb) {
	request(url, function(err, res, body) {
		cb(err, body);
	});
};

Handler.prototype.getAuthored = function(view, url, req, res, cb) {
	var h = this;
	h.get(url, view, {headers: {
		'X-Author': view.key || view.url,
		'Vary': 'X-Author'
	}}, function(err, resource) {
		if (err) return cb(err);
		if (resource.valid) {
			debug("got valid authored html", resource.key || resource.url);
			return cb(null, resource);
		}
		resource.headers['Content-Type'] = 'text/html';
		if (h.authors.before.length || h.authors.current.length || h.authors.after.length) {
			Dom.pool.acquire(function(err, page) {
				if (err) return cb(err);
				debug('view.data length', view.data.length);
				var opts = {
					content: view.data,
					console: true
				};
				if (!Dom.settings.debug) opts.style = Dom.settings.style;
				debug('author preload', url);
				page.preload(url, opts);
				h.processMw(page, resource, h.authors, req, res);
				page.wait('idle').html(function(err, html) {
					debug('author.data length', html && html.length);
					page.locked = false;
					Dom.pool.release(page, function(perr) {
						if (err) return cb(err);
						resource.data = html;
						resource.valid = true;
						resource.mtime = new Date();
						resource.save(cb);
					});
				});
			});
		} else {
			debug("no author plugins");
			resource.data = view.data;
			resource.valid = true;
			resource.mtime = new Date();
			resource.save(cb);
		}
	});
};

Handler.prototype.getUsed = function(author, url, req, res, cb) {
	var h = this;
	h.get(url, author, req, function(err, resource) {
		if (err) return cb(err);
		if (resource.valid) {
			debug("got valid user html", resource.key || resource.url);
			return cb(null, resource);
		}
		if (author.mtime > resource.mtime) {
			debug('author is more recent than user, reload page', resource.url || resource.key);
			delete resource.page;
		}
		Dom.pool.acquire(resource.page, function(err, page) {
			if (err) return cb(err);
			if (!resource.page) {
				resource.page = page;
				var opts = {};
				var customFn;
				for (var key in h.opts) {
					if (key == 'params' && typeof h.opts[key] == 'function') customFn = h.opts[key];
					else opts[key] = h.opts[key];
				}
				if (customFn) customFn(opts, req);
				if (!opts.content) {
					debug('use author data length', author.data.length);
					opts.content = author.data;
				} else {
					debug('use content from customFn data length', opts.content.length);
				}
				for (var k in Dom.settings) if (Dom.settings.hasOwnProperty(k) && opts[k] === undefined) {
					opts[k] = Dom.settings[k];
				}
				debug("user load", resource.key || resource.url, "with stall", opts.stall);
				page.load(resource.url, opts);
				h.processMw(page, resource, h.users, req, res);
			}
			next();
		});
		function next(err) {
			if (err) return cb(err);
			resource.mtime = new Date();
			resource.headers['Content-Type'] = 'text/html';
			var page = resource.page;
			if (!page) return cb(new Error("resource.page is missing for\n" + resource.key));
			page.wait('idle');
			resource.output(page, function(err, str) {
				Dom.pool.unlock(page, function(resource) {
					// breaks the link when the page is recycled
					debug("unlocked page removed from resource", resource.key || resource.url);
					delete resource.page;
				}.bind(this, resource));
				if (err) return cb(err);
				debug('got html', resource.key || resource.url);
				resource.data = str;
				resource.valid = true;
				resource.save(cb);
			});
		}
	});
};

Handler.prototype.processMw = function(page, resource, mwObj, req, res) {
	var list = mwObj.before.concat(mwObj.current).concat(mwObj.after);
	for (var i=0; i < list.length; i++) {
		list[i](page, resource, req, res);
	}
};

function Pool(cacheSize) {
	this.list = [];
	this.max = cacheSize;
	this.extra = 0;
	this.count = 0;
	this.queue = [];
}

Pool.prototype.acquire = function(page, cb) {
	if (page && !cb) {
		cb = page;
		page = null;
	}
	if (page) {
		page.locked = true;
	} else if (this.count < this.max + this.extra) {
		this.count++;
		page = WebKit(Dom.settings);
		page.locked = true;
		this.list.push(page);
	} else {
		for (var i=0; i < this.list.length; i++) {
			page = this.list[i];
			if (!page.locked) {
				page.locked = true;
				if (typeof page.unlock == "function") {
					debug("acquire call page.unlock");
					page.unlock();
					page.removeAllListeners();
					delete page.unlock;
				}
				break;
			}
			page = null;
		}
		if (!page) {
			console.info("Need one extra instance", this.extra++);
			setImmediate(this.process.bind(this));
		}
	}
	if (page) {
		if (this.extra) {
			this.extra--;
			if (this.extra == 0) console.info("No more extra instances, total", this.list.length);
		}
		cb(null, page);
	} else {
		this.queue.push(cb);
	}
};

Pool.prototype.unlock = function(page, unlockCb) {
	debug('unlock called', unlockCb ? 'with' : 'without', 'callback');
	page.unlock = unlockCb;
	page.locked = false;
	setImmediate(this.process.bind(this));
};

Pool.prototype.release = function(page, cb) {
	if (page.locked) return setImmediate(cb);
	page.locked = true; // make sure page cannot be acquired while it is unloaded
	page.unload(function(err) {
		if (page.unlock) {
			debug("release call page.unlock");
			page.unlock();
			delete page.unlock;
		}
		page.removeAllListeners();
		page.locked = false;
		if (cb) cb();
		setImmediate(this.process.bind(this));
	}.bind(this));
};

Pool.prototype.process = function() {
	var next = this.queue.shift();
	if (next) this.acquire(next);
};

function SimpleResource(url) {
	this.url = url;
	this.headers = {};
}
SimpleResource.prototype.save = function(cb) {
	cb(null, this);
};
SimpleResource.prototype.output = function(page, cb) {
	page.html(cb);
};

function isRemote(url) {
	return /^https?:\/\//.test(url);
}

<|endoftext|>


<commit_before>var through = require('through')

module.exports = function BlkDatStream() {
  var buffers = []
  var buffer = new Buffer(0)
  var len = 0
  var needed = 0

  return through(function write(data) {
    len += data.length
    buffers.push(data)

    // do we have a header? and do we have enough data?
    if (needed !== 0 && len < needed) return

    // merge buffers
    buffer = Buffer.concat([buffer].concat(buffers), len)
    buffers = []

    var offset = 0

    // do we [still] have enough data?
    while (len >= needed) {
      // do we need to parse a magic header?
      if (needed === 0) {
        // do we have enough for a magic header?
        if (len < 8) break

        // read the magic header (magic number, block length)
        var magicInt = buffer.readUInt32LE(0)
        var blockLength = buffer.readUInt32LE(4)

        if (magicInt !== 0xd9b4bef9) {
          throw new Error('Unexpected data')
        }

        // now, block length is what is needed
        needed = blockLength
        len -= 8
        offset = 8

        // and loop
        continue
      }

      // read the block
      var block = buffer.slice(offset, offset + needed)

      // process block
      this.queue(block)

      // update cursor information
      buffer = buffer.slice(offset + needed)
      len -= needed
      needed = 0
    }

    // truncate Buffer if magic header was read
    if (needed !== 0) {
      buffer = buffer.slice(offset)
      len = buffer.length
    }
  })
}
<commit_msg>index: rename len to remaining<commit_after>var through = require('through')

module.exports = function BlkDatStream() {
  var buffers = []
  var buffer = new Buffer(0)
  var remaining = 0
  var needed = 0

  return through(function write(data) {
    remaining += data.length
    buffers.push(data)

    // do we have a header? and do we have enough data?
    if (needed !== 0 && remaining < needed) return

    // merge buffers
    buffer = Buffer.concat([buffer].concat(buffers), remaining)
    buffers = []

    var offset = 0

    // do we [still] have enough data?
    while (remaining >= needed) {
      // do we need to parse a magic header?
      if (needed === 0) {
        // do we have enough for a magic header?
        if (remaining < 8) break

        // read the magic header (magic number, block length)
        var magicInt = buffer.readUInt32LE(0)
        var blockLength = buffer.readUInt32LE(4)

        if (magicInt !== 0xd9b4bef9) {
          throw new Error('Unexpected data')
        }

        // now, block length is what is needed
        needed = blockLength
        remaining -= 8
        offset = 8

        // and loop
        continue
      }

      // read the block
      var block = buffer.slice(offset, offset + needed)

      // process block
      this.queue(block)

      // update cursor information
      buffer = buffer.slice(offset + needed)
      remaining -= needed
      needed = 0
    }

    // truncate Buffer if magic header was read
    if (needed !== 0) {
      buffer = buffer.slice(offset)
      remaining = buffer.length
    }
  })
}
<|endoftext|>


<commit_before>'use strict';

var fs		= require('fs');
var http	= require('http');
var exec	= require('child_process').exec;

var serialNumber = function (cb, cmdPrefix) {
	var delimiter = ': ';

	var fromCache = function (error, stdout) {
		fs.readFile(__dirname + '/cache', function (fsErr, data) {
			if (data) {data = data.toString().trim();}
			if (fsErr || !data || data.length < 2) {
				attemptEC2(function() {
					stdoutHandler(error, stdout, true);
				});
			} else {
				cb(null, data);
			}
		});
	};

	var stdoutHandler = function (error, stdout, bypassCache) {
		if (error && !bypassCache) {
			fromCache(error, stdout);
		} else {
			cb(error, parseResult(stdout));
		}
	};

	var parseResult = function (input) {
		result = input.slice(input.indexOf(delimiter) + 2).trim();

		if (result === 'To be filled by O.E.M.') {
			return '';
		}

		return result;
	};

	var attemptEC2 = function (failCb) {
		var data = '';
		var failHandler = function () {
			failCb();
			failCb = function () {};
		};
		var request = http.get(
			'http://169.254.169.254/latest/meta-data/instance-id',
			function (res) {
				res.on('data', function (chunk) {
					data += chunk;
				}).on('end', function () {
					if (data.length > 2) {
						cb(null, data.trim());
					} else {
						failHandler();
					}
				});
			}
		);
		request.on('error', failHandler).setTimeout(1000, failHandler);
	};

	cmdPrefix = cmdPrefix || '';
	var vals = ['Serial', 'UUID'];
	var cmd;

	switch (process.platform) {

	case 'win32':
		delimiter = '\r\n';
		vals[0] = 'IdentifyingNumber';
		cmd = 'wmic csproduct get ';
		break;

	case 'darwin':
		cmd = 'system_profiler SPHardwareDataType | grep ';
		break;

	case 'linux':
		if (process.arch === 'arm') {
			vals[1] = 'Serial';
			cmd = 'cat /proc/cpuinfo | grep ';

		} else {
			cmd = 'dmidecode -t system | grep ';
		}
		break;

	case 'freebsd':
		cmd = 'dmidecode -t system | grep ';
		break;
	}

	if (!cmd) return cb(new Error('Cannot provide serial number for ' + process.platform));

	if (serialNumber.preferUUID) vals.reverse();

	exec(cmdPrefix + cmd + vals[0], function (error, stdout) {
		if (error || parseResult(stdout).length > 1) {
			stdoutHandler(error, stdout);
		} else {
			exec(cmdPrefix + cmd + vals[1], stdoutHandler);
		}
	});
};

serialNumber.preferUUID = false;

module.exports = exports = serialNumber;

exports.useSudo = function (cb) {
	serialNumber(cb, 'sudo ');
};
<commit_msg>easier to add useless serialss in array<commit_after>'use strict';

var fs		= require('fs');
var http	= require('http');
var exec	= require('child_process').exec;

var serialNumber = function (cb, cmdPrefix) {
	var delimiter = ': ';
	var uselessSerials = [
		'To be filled by O.E.M.',
	]

	var fromCache = function (error, stdout) {
		fs.readFile(__dirname + '/cache', function (fsErr, data) {
			if (data) {data = data.toString().trim();}
			if (fsErr || !data || data.length < 2) {
				attemptEC2(function() {
					stdoutHandler(error, stdout, true);
				});
			} else {
				cb(null, data);
			}
		});
	};

	var stdoutHandler = function (error, stdout, bypassCache) {
		if (error && !bypassCache) {
			fromCache(error, stdout);
		} else {
			cb(error, parseResult(stdout));
		}
	};

	var parseResult = function (input) {
		result = input.slice(input.indexOf(delimiter) + 2).trim();

		if (result in uselessSerials) {
			return '';
		}

		return result;
	};

	var attemptEC2 = function (failCb) {
		var data = '';
		var failHandler = function () {
			failCb();
			failCb = function () {};
		};
		var request = http.get(
			'http://169.254.169.254/latest/meta-data/instance-id',
			function (res) {
				res.on('data', function (chunk) {
					data += chunk;
				}).on('end', function () {
					if (data.length > 2) {
						cb(null, data.trim());
					} else {
						failHandler();
					}
				});
			}
		);
		request.on('error', failHandler).setTimeout(1000, failHandler);
	};

	cmdPrefix = cmdPrefix || '';
	var vals = ['Serial', 'UUID'];
	var cmd;

	switch (process.platform) {

	case 'win32':
		delimiter = '\r\n';
		vals[0] = 'IdentifyingNumber';
		cmd = 'wmic csproduct get ';
		break;

	case 'darwin':
		cmd = 'system_profiler SPHardwareDataType | grep ';
		break;

	case 'linux':
		if (process.arch === 'arm') {
			vals[1] = 'Serial';
			cmd = 'cat /proc/cpuinfo | grep ';

		} else {
			cmd = 'dmidecode -t system | grep ';
		}
		break;

	case 'freebsd':
		cmd = 'dmidecode -t system | grep ';
		break;
	}

	if (!cmd) return cb(new Error('Cannot provide serial number for ' + process.platform));

	if (serialNumber.preferUUID) vals.reverse();

	exec(cmdPrefix + cmd + vals[0], function (error, stdout) {
		if (error || parseResult(stdout).length > 1) {
			stdoutHandler(error, stdout);
		} else {
			exec(cmdPrefix + cmd + vals[1], stdoutHandler);
		}
	});
};

serialNumber.preferUUID = false;

module.exports = exports = serialNumber;

exports.useSudo = function (cb) {
	serialNumber(cb, 'sudo ');
};
<|endoftext|>


<commit_before>'use strict';

const isFirefoxAndroid = require('is-firefox-android')();
const handleClick = require('./lib/handle-click');
const { PageMod } = require('sdk/page-mod');
const tabs = require('sdk/tabs');
const data = require('sdk/self').data;
let button;
let menuId;

if (isFirefoxAndroid) {
  const getWindow = require('get-firefox-browser-window');
  menuId = 0;
  exports.main = (options, callback) => {// eslint-disable-line no-unused-vars
    menuId = getWindow().NativeWindow.menu.add({
      name: 'Page for Hatebu (-)',
      callback: handleClick,
    });
  };
  exports.onUnload = (reason) => {// eslint-disable-line no-unused-vars
    getWindow().NativeWindow.menu.remove(menuId);
  };
} else {
  const { ActionButton } = require('sdk/ui/button/action');
  button = ActionButton({// eslint-disable-line new-cap
    id: 'page-for-hatena-bookmark',
    label: 'Page for Hatebu',
    icon: {
      16: './bookmark42-16.png',
      32: './bookmark42-32.png',
      64: './bookmark42-64.png',
    },
    onClick: handleClick,
    badge: '',
    badgeColor: '#696969',
  });
}

PageMod({// eslint-disable-line new-cap
  include: '*',
  contentScriptFile: [
    data.url('../node_modules/browser-canonical-url/build/browser-canonical-url.js'),
    data.url('content-script.js'),
  ],
  attachTo: ['existing', 'top'],
  onAttach: (worker) => {
    worker.port.on('canonicalUrl', (request) => {
      button.badge = 1;
      console.log(`page-loaded: ${request}`);// eslint-disable-line no-console
    });
    worker.port.emit('getCanonicalUrl');
  },
});

tabs.on('activate', (tab) => {
  const worker = tab.attach({
    contentScriptFile: [
      data.url('../node_modules/browser-canonical-url/build/browser-canonical-url.js'),
      data.url('content-script.js'),
    ],
  });
  worker.port.on('canonicalUrl', (request) => {
    console.log(request);// eslint-disable-line no-console
    button.badge = Math.floor(Math.random() * 50);
  });
  worker.port.emit('getCanonicalUrl');
});
<commit_msg>refactor(page): extract function<commit_after>'use strict';

const isFirefoxAndroid = require('is-firefox-android')();
const handleClick = require('./lib/handle-click');
const { PageMod } = require('sdk/page-mod');
const tabs = require('sdk/tabs');
const data = require('sdk/self').data;
let button;
let menuId;
let page;

if (isFirefoxAndroid) {
  const getWindow = require('get-firefox-browser-window');
  menuId = 0;
  exports.main = (options, callback) => {// eslint-disable-line no-unused-vars
    menuId = getWindow().NativeWindow.menu.add({
      name: 'Page for Hatebu (-)',
      callback: handleClick,
    });
  };
  exports.onUnload = (reason) => {// eslint-disable-line no-unused-vars
    getWindow().NativeWindow.menu.remove(menuId);
  };
} else {
  const { ActionButton } = require('sdk/ui/button/action');
  button = ActionButton({// eslint-disable-line new-cap
    id: 'page-for-hatena-bookmark',
    label: 'Page for Hatebu',
    icon: {
      16: './bookmark42-16.png',
      32: './bookmark42-32.png',
      64: './bookmark42-64.png',
    },
    onClick: handleClick,
    badge: '',
    badgeColor: '#696969',
  });
}

page = PageMod({// eslint-disable-line new-cap
  include: '*',
  contentScriptFile: [
    data.url('../node_modules/browser-canonical-url/build/browser-canonical-url.js'),
    data.url('content-script.js'),
  ],
  attachTo: ['existing', 'top'],
});

page.on('attach', (worker) => {
  worker.port.on('canonicalUrl', (request) => {
    button.badge = 1;
    console.log(`page-loaded: ${request}`);// eslint-disable-line no-console
  });
  worker.port.emit('getCanonicalUrl');
});

tabs.on('activate', (tab) => {
  const worker = tab.attach({
    contentScriptFile: [
      data.url('../node_modules/browser-canonical-url/build/browser-canonical-url.js'),
      data.url('content-script.js'),
    ],
  });
  worker.port.on('canonicalUrl', (request) => {
    console.log(request);// eslint-disable-line no-console
    button.badge = Math.floor(Math.random() * 50);
  });
  worker.port.emit('getCanonicalUrl');
});
<|endoftext|>


<commit_before>'use strict'

const Dissolve = require('dissolve')

class ParserError extends Error {
  constructor (message) {
    super(message)
    this.name = 'ParserError'
  }
}

class Parser extends Dissolve {
  constructor () {
    super()

    this.tagMap = this.setupTagMap()

    this.start()
  }

  /**
   * Returns a list of supported tags with their name and the appropriate parsing method to call.
   *
   * @returns {Object}
   */
  setupTagMap () {
    return {
      70: {
        impl: this.doublebe,
        name: 'newFloat'
      },
      // FIXME: doesn't work yet as the bits field isn't necessary a multiple of 8
      /*77: {
        impl: this.complexTag,
        name: 'bitBinary',
        defs: [
          {
            impl: this.uint32be,
            name: 'len'
          },
          {
            impl: this.uint8be,
            name: 'bits'
          },
          {
            impl: this.???,
            name: 'data'
          }
        ]
      },*/
      82: {
        impl: this.uint8,
        name: 'atomCacheRef'
      },
      97: {
        impl: this.uint8,
        name: 'smallInteger'
      },
      98: {
        impl: this.uint32be,
        name: 'integer'
      },
      99: {
        impl: this.float31,
        name: 'float'
      },
      100: {
        impl: this.complexTag,
        name: 'atom',
        def: [
          {
            impl: this.uint16be,
            name: 'len'
          },
          {
            impl: this.string,
            name: 'atom',
            varsArgs: ['len']
          }
        ]
      },
      101: {
        impl: this.complexTag,
        name: 'reference',
        def: [
          {
            impl: this.tag,
            name: 'node'
          },
          {
            impl: this.uint32be,
            name: 'id'
          },
          {
            impl: this.uint8,
            name: 'creation'
          }
        ]
      },
      102: {
        impl: this.complexTag,
        name: 'port',
        def: [
          {
            impl: this.tag,
            name: 'node'
          },
          {
            impl: this.uint32be,
            name: 'id'
          },
          {
            impl: this.uint8,
            name: 'creation'
          }
        ]
      },
      103: {
        impl: this.complexTag,
        name: 'pid',
        def: [
          {
            impl: this.tag,
            name: 'node'
          },
          {
            impl: this.uint32be,
            name: 'id'
          },
          {
            impl: this.uint32be,
            name: 'serial'
          },
          {
            impl: this.uint8,
            name: 'creation'
          }
        ]
      },
      104: {
        impl: this.complexTag,
        name: 'smallTuple',
        def: [
          {
            impl: this.uint8,
            name: 'arity'
          },
          {
            impl: this.tagList,
            name: 'elements',
            varsArgs: ['arity']
          }
        ]
      },
      105: {
        impl: this.complexTag,
        name: 'largeTuple',
        def: [
          {
            impl: this.uint32be,
            name: 'arity'
          },
          {
            impl: this.tagList,
            name: 'elements',
            varsArgs: ['arity']
          }
        ]
      },
      // FIXME: this probably doesn't work
      106: {
        impl: this.nil,
        name: 'nil'
      },
      107: {
        impl: this.complexTag,
        name: 'string',
        def: [
          {
            impl: this.uint16be,
            name: 'length'
          },
          {
            impl: this.string,
            name: 'characters',
            varsArgs: ['length']
          }
        ]
      },
      108: {
        impl: this.complexTag,
        name: 'list',
        def: [
          {
            impl: this.uint32be,
            name: 'length'
          },
          {
            impl: this.tagList,
            name: 'elements',
            varsArgs: ['length']
          },
          {
            impl: this.tag,
            name: 'tail'
          }
        ]
      },
      109: {
        impl: this.complexTag,
        name: 'binary',
        def: [
          {
            impl: this.uint32be,
            name: 'len'
          },
          {
            impl: this.buffer,
            name: 'data',
            varsArgs: ['len']
          }
        ]
      },
      110: {
        impl: this.complexTag,
        name: 'smallBigNum', // how ironic
        def: [
          {
            impl: this.uint8,
            name: 'n'
          },
          {
            impl: this.bool,
            name: 'sign'
          },
          {
            impl: this.bigNum,
            name: 'number',
            varsArgs: ['n', 'sign']
          }
        ]
      },
      111: {
        impl: this.complexTag,
        name: 'largeBigNum',
        def: [
          {
            impl: this.uint32be,
            name: 'n'
          },
          {
            impl: this.bool,
            name: 'sign'
          },
          {
            impl: this.bigNum,
            name: 'number',
            varsArgs: ['n', 'sign']
          }
        ]
      },
      112: {
        impl: this.complexTag,
        name: 'newFun',
        def: [
          {
            impl: this.uint32be,
            name: 'size'
          },
          {
            impl: this.uint8,
            name: 'arity'
          },
          {
            impl: this.string,
            name: 'uniq',
            args: [16]
          },
          {
            impl: this.uint32be,
            name: 'index'
          },
          {
            impl: this.uint32be,
            name: 'numFree'
          },
          {
            impl: this.tag,
            name: 'module'
          },
          {
            impl: this.tag,
            name: 'oldIndex'
          },
          {
            impl: this.tag,
            name: 'oldUniq'
          },
          {
            impl: this.tag,
            name: 'pid'
          },
          {
            impl: this.tagList,
            name: 'freeVars',
            varsArgs: ['numFree']
          }
        ]
      },
      113: {
        impl: this.complexTag,
        name: 'export',
        def: [
          {
            impl: this.tag,
            name: 'module'
          },
          {
            impl: this.tag,
            name: 'function'
          },
          {
            impl: this.tag,
            name: 'arity'
          }
        ]
      },
      114: {
        impl: this.complexTag,
        name: 'newReference',
        def: [
          {
            impl: this.uint16be,
            name: 'len'
          },
          {
            impl: this.tag,
            name: 'node'
          },
          {
            impl: this.uint8,
            name: 'creation'
          },
          {
            impl: this.uint32beLoop,
            name: 'id',
            varsArgs: ['len']
          }
        ]
      },
      115: {
        impl: this.complexTag,
        name: 'smallAtom',
        def: [
          {
            impl: this.uint8,
            name: 'len'
          },
          {
            impl: this.string,
            name: 'atom',
            varsArgs: ['len']
          }
        ]
      },
      116: {
        impl: this.complexTag,
        name: 'map',
        def: [
          {
            impl: this.uint32be,
            name: 'arity'
          },
          {
            impl: this.map,
            name: 'pairs',
            varsArgs: ['arity']
          }
        ]
      },
      117: {
        impl: this.complexTag,
        name: 'fun',
        def: [
          {
            impl: this.uint32be,
            name: 'numFree'
          },
          {
            impl: this.tag,
            name: 'pid'
          },
          {
            impl: this.tag,
            name: 'module'
          },
          {
            impl: this.tag,
            name: 'index'
          },
          {
            impl: this.tag,
            name: 'uniq'
          },
          {
            impl: this.tagList,
            name: 'freeVars',
            varsArgs: ['numFree']
          }
        ]
      },
      118: {
        impl: this.complexTag,
        name: 'utf8Atom',
        def: [
          {
            impl: this.uint16be,
            name: 'len'
          },
          {
            impl: this.string,
            name: 'atom',
            varsArgs: ['len']
          }
        ]
      },
      119: {
        impl: this.complexTag,
        name: 'smallUtf8Atom',
        def: [
          {
            impl: this.uint8,
            name: 'len'
          },
          {
            impl: this.string,
            name: 'atom',
            varsArgs: ['len']
          }
        ]
      }
    }
  }

  /**
   * Starts the parsing
   *
   * @returns {this}
   */
  start () {
    return this.tagLoop(() => {
      Object.keys(this.vars).map((key) => {
        this.push(this.vars[key])
      })
    })
  }

  /**
   * Loops through the buffer and finds Erlang ext format tags
   *
   * @param {Function} cb      Callback to call after each tag found.
   * @param {number}   [limit] Limit number of tags to be found. If not passed, there's no limit and it loops until
   *                           the end of the buffer.
   *
   * @returns {this}
   */
  tagLoop (cb, limit) {
    return this.tag().tap(() => {

      cb()
      this.vars = {}

      if (limit === undefined || --limit > 0) {
        return this.tagLoop(cb, limit)
      }
    })
  }

  /**
   * List of tags
   *
   * Converts a list of tags followed after each other into an array.
   *
   * @param {string} name    Target name of the array in the list of vars. Unused.
   * @param {number} [limit] How many tags are followed after each other.
   *
   * @returns {this}
   */
  tagList (name, limit) {
    let vars = []

    return this.tagLoop(() => {
      Object.keys(this.vars).forEach((key) => {
        vars.push(this.vars[key])
      })
    }, limit).tap(() => {
      this.vars = vars
    })
  }

  /**
   * Parses a specific erlang type tag
   *
   * @returns {this}
   */
  tag () {
    return this.uint8('tag').tap(() => {
      const tag = this.tagMap[this.vars.tag]
      if (!tag) {
        throw new ParserError(`Unable to read tag ${this.vars.tag}`)
      }
      tag.impl.call(this, tag.name, tag.def)
    }).tap(() => {
      delete this.vars.tag

      let vars = this.vars
      this.vars = {}

      Object.keys(vars).forEach((key) => {
        this.vars[key] = {
          name: key,
          value: vars[key]
        }
      })
    })
  }

  /**
   * Complex tag is a tag which holds several tags each of which has
   * a different implementation specified.
   *
   * @param {string} name Name of complex tag
   * @param {Array}  def  Definition of the tag
   *
   * @returns {this}
   */
  complexTag (name, def) {
    let vars = {}
    let collection = def.slice(0) // clone array

    return this.parseDef(name, vars, collection)
  }

  /**
   * Parses the definition of a complex tag
   *
   * @param {string} name Complex tag name
   * @param {Object} vars List of variables
   * @param {Array}  defs List of definitions
   *
   * @returns {this}
   */
  parseDef (name, vars, defs) {
    const val = defs.splice(0, 1)[0]

    let args = [val.name]
    if (val.varsArgs) {
      val.varsArgs.forEach((key) => {
        args.push(vars[key])
      })
    }
    if (val.args) {
      args = args.concat(val.args)
    }

    return val.impl.apply(this, args).tap(() => {
      if (Array.isArray(this.vars)) {
        vars[val.name] = this.vars
      } else {
        Object.keys(this.vars).forEach((key) => {
          vars[val.name] = this.vars[key]
        })
      }

      this.vars = {}

      if (defs.length > 0) {
        return this.parseDef(name, vars, defs)
      }
      this.vars[name] = vars
    })
  }

  /**
   * Parses a map by using the tagList but then fiddles with the created array
   * to return a map-like structure where each item in the array is an object with key and value.
   *
   * @param {string} name  Map name
   * @param {number} arity Arity - number of pairs
   *
   * @returns {this}
   */
  map (name, arity) {
    let vars = []
    const numPairs = arity * 2

    return this.tagList(name, numPairs).tap(() => {
      let currentKey = null
      for (let i = 0, l = this.vars.length; i < l; i++) {
        if (i % 2 === 0) {
          currentKey = this.vars[i]
        } else {
          vars.push({
            key: currentKey,
            value: this.vars[i]
          })
        }
      }

      this.vars = {}
      this.vars[name] = vars
    })
  }

  /**
   * Float encoded as a string of length 31.
   *
   * @param {string} name Name of the tag
   *
   * @returns {this}
   */
  float31 (name) {
    return this.string(name, 31).tap(() => {
      this.vars[name] = parseFloat(this.vars[name])
    })
  }

  /**
   * Boolean uint8
   *
   * @param {string} name Tag name
   *
   * @returns {this}
   */
  bool (name) {
    return this.uint8(name).tap(() => {
      this.vars[name] = this.vars[name] === 1
    })
  }

  /**
   * Bignum
   *
   * @param {string} name Tag name
   * @param {number} n    Number of characters
   * @param {bool}   sign False = negative, true = positive
   *
   * @returns {this}
   */
  bigNum (name, n, sign) {
    const B = 256 // per protocol documentation

    return this.string(name, n).tap(() => {
      let i = 0
      let val = 0
      for (var char of this.vars[name]) {
        val += parseInt(char, 10) * Math.pow(B, i)
        i++
      }

      if (sign) {
        val = val * -1
      }
      this.vars[name] = val
    })
  }

  /**
   * Puts in a null value
   *
   * @param {string} name
   *
   * @returns {this}
   */
  nil (name) {
    this.vars[name] = null

    return this
  }

  /**
   * Loop of unsigned 4 byte big-endian integers.
   *
   * @param {string} name Name of the key in vars object
   * @param {number} len  Number of iterations
   * @param {Array}  vars Temporary storage for found integers
   *
   * @returns {this}
   */
  uint32beLoop (name, len, vars) {
    vars = vars || []
    return this.uint32be(name).tap(() => {
      vars.push(this.vars[name])
      this.vars = {}

      if (--len > 0) {
        return this.uint32beLoop(name, len, vars)
      }

      this.vars[name] = vars
    })
  }
}

module.exports = Parser<commit_msg>docs(parser): Remove outdated FIXME comment<commit_after>'use strict'

const Dissolve = require('dissolve')

class ParserError extends Error {
  constructor (message) {
    super(message)
    this.name = 'ParserError'
  }
}

class Parser extends Dissolve {
  constructor () {
    super()

    this.tagMap = this.setupTagMap()

    this.start()
  }

  /**
   * Returns a list of supported tags with their name and the appropriate parsing method to call.
   *
   * @returns {Object}
   */
  setupTagMap () {
    return {
      70: {
        impl: this.doublebe,
        name: 'newFloat'
      },
      // FIXME: doesn't work yet as the bits field isn't necessary a multiple of 8
      /*77: {
        impl: this.complexTag,
        name: 'bitBinary',
        defs: [
          {
            impl: this.uint32be,
            name: 'len'
          },
          {
            impl: this.uint8be,
            name: 'bits'
          },
          {
            impl: this.???,
            name: 'data'
          }
        ]
      },*/
      82: {
        impl: this.uint8,
        name: 'atomCacheRef'
      },
      97: {
        impl: this.uint8,
        name: 'smallInteger'
      },
      98: {
        impl: this.uint32be,
        name: 'integer'
      },
      99: {
        impl: this.float31,
        name: 'float'
      },
      100: {
        impl: this.complexTag,
        name: 'atom',
        def: [
          {
            impl: this.uint16be,
            name: 'len'
          },
          {
            impl: this.string,
            name: 'atom',
            varsArgs: ['len']
          }
        ]
      },
      101: {
        impl: this.complexTag,
        name: 'reference',
        def: [
          {
            impl: this.tag,
            name: 'node'
          },
          {
            impl: this.uint32be,
            name: 'id'
          },
          {
            impl: this.uint8,
            name: 'creation'
          }
        ]
      },
      102: {
        impl: this.complexTag,
        name: 'port',
        def: [
          {
            impl: this.tag,
            name: 'node'
          },
          {
            impl: this.uint32be,
            name: 'id'
          },
          {
            impl: this.uint8,
            name: 'creation'
          }
        ]
      },
      103: {
        impl: this.complexTag,
        name: 'pid',
        def: [
          {
            impl: this.tag,
            name: 'node'
          },
          {
            impl: this.uint32be,
            name: 'id'
          },
          {
            impl: this.uint32be,
            name: 'serial'
          },
          {
            impl: this.uint8,
            name: 'creation'
          }
        ]
      },
      104: {
        impl: this.complexTag,
        name: 'smallTuple',
        def: [
          {
            impl: this.uint8,
            name: 'arity'
          },
          {
            impl: this.tagList,
            name: 'elements',
            varsArgs: ['arity']
          }
        ]
      },
      105: {
        impl: this.complexTag,
        name: 'largeTuple',
        def: [
          {
            impl: this.uint32be,
            name: 'arity'
          },
          {
            impl: this.tagList,
            name: 'elements',
            varsArgs: ['arity']
          }
        ]
      },
      106: {
        impl: this.nil,
        name: 'nil'
      },
      107: {
        impl: this.complexTag,
        name: 'string',
        def: [
          {
            impl: this.uint16be,
            name: 'length'
          },
          {
            impl: this.string,
            name: 'characters',
            varsArgs: ['length']
          }
        ]
      },
      108: {
        impl: this.complexTag,
        name: 'list',
        def: [
          {
            impl: this.uint32be,
            name: 'length'
          },
          {
            impl: this.tagList,
            name: 'elements',
            varsArgs: ['length']
          },
          {
            impl: this.tag,
            name: 'tail'
          }
        ]
      },
      109: {
        impl: this.complexTag,
        name: 'binary',
        def: [
          {
            impl: this.uint32be,
            name: 'len'
          },
          {
            impl: this.buffer,
            name: 'data',
            varsArgs: ['len']
          }
        ]
      },
      110: {
        impl: this.complexTag,
        name: 'smallBigNum', // how ironic
        def: [
          {
            impl: this.uint8,
            name: 'n'
          },
          {
            impl: this.bool,
            name: 'sign'
          },
          {
            impl: this.bigNum,
            name: 'number',
            varsArgs: ['n', 'sign']
          }
        ]
      },
      111: {
        impl: this.complexTag,
        name: 'largeBigNum',
        def: [
          {
            impl: this.uint32be,
            name: 'n'
          },
          {
            impl: this.bool,
            name: 'sign'
          },
          {
            impl: this.bigNum,
            name: 'number',
            varsArgs: ['n', 'sign']
          }
        ]
      },
      112: {
        impl: this.complexTag,
        name: 'newFun',
        def: [
          {
            impl: this.uint32be,
            name: 'size'
          },
          {
            impl: this.uint8,
            name: 'arity'
          },
          {
            impl: this.string,
            name: 'uniq',
            args: [16]
          },
          {
            impl: this.uint32be,
            name: 'index'
          },
          {
            impl: this.uint32be,
            name: 'numFree'
          },
          {
            impl: this.tag,
            name: 'module'
          },
          {
            impl: this.tag,
            name: 'oldIndex'
          },
          {
            impl: this.tag,
            name: 'oldUniq'
          },
          {
            impl: this.tag,
            name: 'pid'
          },
          {
            impl: this.tagList,
            name: 'freeVars',
            varsArgs: ['numFree']
          }
        ]
      },
      113: {
        impl: this.complexTag,
        name: 'export',
        def: [
          {
            impl: this.tag,
            name: 'module'
          },
          {
            impl: this.tag,
            name: 'function'
          },
          {
            impl: this.tag,
            name: 'arity'
          }
        ]
      },
      114: {
        impl: this.complexTag,
        name: 'newReference',
        def: [
          {
            impl: this.uint16be,
            name: 'len'
          },
          {
            impl: this.tag,
            name: 'node'
          },
          {
            impl: this.uint8,
            name: 'creation'
          },
          {
            impl: this.uint32beLoop,
            name: 'id',
            varsArgs: ['len']
          }
        ]
      },
      115: {
        impl: this.complexTag,
        name: 'smallAtom',
        def: [
          {
            impl: this.uint8,
            name: 'len'
          },
          {
            impl: this.string,
            name: 'atom',
            varsArgs: ['len']
          }
        ]
      },
      116: {
        impl: this.complexTag,
        name: 'map',
        def: [
          {
            impl: this.uint32be,
            name: 'arity'
          },
          {
            impl: this.map,
            name: 'pairs',
            varsArgs: ['arity']
          }
        ]
      },
      117: {
        impl: this.complexTag,
        name: 'fun',
        def: [
          {
            impl: this.uint32be,
            name: 'numFree'
          },
          {
            impl: this.tag,
            name: 'pid'
          },
          {
            impl: this.tag,
            name: 'module'
          },
          {
            impl: this.tag,
            name: 'index'
          },
          {
            impl: this.tag,
            name: 'uniq'
          },
          {
            impl: this.tagList,
            name: 'freeVars',
            varsArgs: ['numFree']
          }
        ]
      },
      118: {
        impl: this.complexTag,
        name: 'utf8Atom',
        def: [
          {
            impl: this.uint16be,
            name: 'len'
          },
          {
            impl: this.string,
            name: 'atom',
            varsArgs: ['len']
          }
        ]
      },
      119: {
        impl: this.complexTag,
        name: 'smallUtf8Atom',
        def: [
          {
            impl: this.uint8,
            name: 'len'
          },
          {
            impl: this.string,
            name: 'atom',
            varsArgs: ['len']
          }
        ]
      }
    }
  }

  /**
   * Starts the parsing
   *
   * @returns {this}
   */
  start () {
    return this.tagLoop(() => {
      Object.keys(this.vars).map((key) => {
        this.push(this.vars[key])
      })
    })
  }

  /**
   * Loops through the buffer and finds Erlang ext format tags
   *
   * @param {Function} cb      Callback to call after each tag found.
   * @param {number}   [limit] Limit number of tags to be found. If not passed, there's no limit and it loops until
   *                           the end of the buffer.
   *
   * @returns {this}
   */
  tagLoop (cb, limit) {
    return this.tag().tap(() => {

      cb()
      this.vars = {}

      if (limit === undefined || --limit > 0) {
        return this.tagLoop(cb, limit)
      }
    })
  }

  /**
   * List of tags
   *
   * Converts a list of tags followed after each other into an array.
   *
   * @param {string} name    Target name of the array in the list of vars. Unused.
   * @param {number} [limit] How many tags are followed after each other.
   *
   * @returns {this}
   */
  tagList (name, limit) {
    let vars = []

    return this.tagLoop(() => {
      Object.keys(this.vars).forEach((key) => {
        vars.push(this.vars[key])
      })
    }, limit).tap(() => {
      this.vars = vars
    })
  }

  /**
   * Parses a specific erlang type tag
   *
   * @returns {this}
   */
  tag () {
    return this.uint8('tag').tap(() => {
      const tag = this.tagMap[this.vars.tag]
      if (!tag) {
        throw new ParserError(`Unable to read tag ${this.vars.tag}`)
      }
      tag.impl.call(this, tag.name, tag.def)
    }).tap(() => {
      delete this.vars.tag

      let vars = this.vars
      this.vars = {}

      Object.keys(vars).forEach((key) => {
        this.vars[key] = {
          name: key,
          value: vars[key]
        }
      })
    })
  }

  /**
   * Complex tag is a tag which holds several tags each of which has
   * a different implementation specified.
   *
   * @param {string} name Name of complex tag
   * @param {Array}  def  Definition of the tag
   *
   * @returns {this}
   */
  complexTag (name, def) {
    let vars = {}
    let collection = def.slice(0) // clone array

    return this.parseDef(name, vars, collection)
  }

  /**
   * Parses the definition of a complex tag
   *
   * @param {string} name Complex tag name
   * @param {Object} vars List of variables
   * @param {Array}  defs List of definitions
   *
   * @returns {this}
   */
  parseDef (name, vars, defs) {
    const val = defs.splice(0, 1)[0]

    let args = [val.name]
    if (val.varsArgs) {
      val.varsArgs.forEach((key) => {
        args.push(vars[key])
      })
    }
    if (val.args) {
      args = args.concat(val.args)
    }

    return val.impl.apply(this, args).tap(() => {
      if (Array.isArray(this.vars)) {
        vars[val.name] = this.vars
      } else {
        Object.keys(this.vars).forEach((key) => {
          vars[val.name] = this.vars[key]
        })
      }

      this.vars = {}

      if (defs.length > 0) {
        return this.parseDef(name, vars, defs)
      }
      this.vars[name] = vars
    })
  }

  /**
   * Parses a map by using the tagList but then fiddles with the created array
   * to return a map-like structure where each item in the array is an object with key and value.
   *
   * @param {string} name  Map name
   * @param {number} arity Arity - number of pairs
   *
   * @returns {this}
   */
  map (name, arity) {
    let vars = []
    const numPairs = arity * 2

    return this.tagList(name, numPairs).tap(() => {
      let currentKey = null
      for (let i = 0, l = this.vars.length; i < l; i++) {
        if (i % 2 === 0) {
          currentKey = this.vars[i]
        } else {
          vars.push({
            key: currentKey,
            value: this.vars[i]
          })
        }
      }

      this.vars = {}
      this.vars[name] = vars
    })
  }

  /**
   * Float encoded as a string of length 31.
   *
   * @param {string} name Name of the tag
   *
   * @returns {this}
   */
  float31 (name) {
    return this.string(name, 31).tap(() => {
      this.vars[name] = parseFloat(this.vars[name])
    })
  }

  /**
   * Boolean uint8
   *
   * @param {string} name Tag name
   *
   * @returns {this}
   */
  bool (name) {
    return this.uint8(name).tap(() => {
      this.vars[name] = this.vars[name] === 1
    })
  }

  /**
   * Bignum
   *
   * @param {string} name Tag name
   * @param {number} n    Number of characters
   * @param {bool}   sign False = negative, true = positive
   *
   * @returns {this}
   */
  bigNum (name, n, sign) {
    const B = 256 // per protocol documentation

    return this.string(name, n).tap(() => {
      let i = 0
      let val = 0
      for (var char of this.vars[name]) {
        val += parseInt(char, 10) * Math.pow(B, i)
        i++
      }

      if (sign) {
        val = val * -1
      }
      this.vars[name] = val
    })
  }

  /**
   * Puts in a null value
   *
   * @param {string} name
   *
   * @returns {this}
   */
  nil (name) {
    this.vars[name] = null

    return this
  }

  /**
   * Loop of unsigned 4 byte big-endian integers.
   *
   * @param {string} name Name of the key in vars object
   * @param {number} len  Number of iterations
   * @param {Array}  vars Temporary storage for found integers
   *
   * @returns {this}
   */
  uint32beLoop (name, len, vars) {
    vars = vars || []
    return this.uint32be(name).tap(() => {
      vars.push(this.vars[name])
      this.vars = {}

      if (--len > 0) {
        return this.uint32beLoop(name, len, vars)
      }

      this.vars[name] = vars
    })
  }
}

module.exports = Parser<|endoftext|>


<commit_before>//const qsocks = require('qsocks');
const enigma = require('enigma.js');
const WebSocket = require('ws');
const fs = require('fs');
const util = require('util')
var qrsInteract = require('qrs-interact');
var request = require('request');
var restify = require('restify');
var winston = require('winston');
var config = require('config');

// Set up Winston logger, logging both to console and different disk files
var logger = new (winston.Logger)({
    transports: [
        new (winston.transports.Console)({
            name: 'console_log',
            'timestamp': true,
            'colorize': true
        }),
        new (winston.transports.File)({
            name: 'file_info',
            filename: config.get('logDirectory') + '/info.log',
            level: 'info'
        }),
        new (winston.transports.File)({
            name: 'file_verbose',
            filename: config.get('logDirectory') + '/verbose.log',
            level: 'verbose'
        }),
        new (winston.transports.File)({
            name: 'file_error',
            filename: config.get('logDirectory') + '/error.log',
            level: 'error'
        })
    ]
});

// Set default log level
logger.transports.console_log.level = config.get('defaultLogLevel');

logger.log('info', 'Starting Qlik Sense template app duplicator.');

// Read certificates
const client = fs.readFileSync(config.get('clientCertPath'));
const client_key = fs.readFileSync(config.get('clientCertKeyPath'));
// const rootCert = fs.readFileSync(config.get('clientCertRootPath'));

// Read load script from wherever it is stored (Github etc)
const loadScriptURL = config.get('loadScriptURL');

// Set up enigma.js configuration
const qixSchema = require('enigma.js/schemas/qix/3.2/schema.json');
const configEnigma = {
    schema: qixSchema,
    session: {
        host: config.get('host'),
        port: 4747, // Standard Engine port
        secure: config.get('isSecure'),
        disableCache: true
    },
    createSocket: (url, sessionConfig) => {
        return new WebSocket(url, {
            // ca: rootCert,
            key: client_key,
            cert: client,
            headers: {
                'X-Qlik-User': 'UserDirectory=Internal;UserId=sa_repository'
            },
            rejectUnauthorized: false
        });
    }
}

// Set up Sense repository service configuration
var configQRS = {
    hostname: config.get('host'),
    certificates: {
        certFile: config.get('clientCertPath'),
        keyFile: config.get('clientCertKeyPath'),
    }
}

var restServer = restify.createServer({
    name: 'Qlik Sense app duplicator',
    version: '1.1.0',
    certificate: fs.readFileSync(config.get('sslCertPath')),
    key: fs.readFileSync(config.get('sslCertKeyPath'))
});

// Enable parsing of http parameters
restServer.use(restify.queryParser());

// Set up CORS handling
restServer.use(restify.CORS({ origins: ['*'] }));

// Set up endpoints for REST server
restServer.get('/duplicateNewScript', respondDuplicateNewScript);
restServer.get('/duplicateKeepScript', respondDuplicateKeepScript);
restServer.get('/getTemplateList', respondGetTemplateList);

// Start the server
restServer.listen(8000, function () {
    console.log('%s listening at %s', restServer.name, restServer.url);
});

// Handler for REST endpoint /getTemplateList
// URL parameters
//   -- None --
function respondGetTemplateList(req, res, next) {
    configQRS.headers = { 'X-Qlik-User': 'UserDirectory=Internal; UserId=sa_repository' };
    logger.log('verbose', configQRS);

    var qrsInteractInstance = new qrsInteract(configQRS);

    var appList = [];

    qrsInteractInstance.Get("app/full?filter=@AppIsTemplate eq 'Yes'")
        .then(result => {
            logger.log('debug', 'result=' + result);

            result.body.forEach(function (element) {
                appList.push({
                    name: element.name,
                    id: element.id,
                    description: element.description
                });

                logger.log('verbose', 'Element name: ' + element.name);
                logger.log('verbose', 'App list JSON: ' + JSON.stringify(appList));
            }, this);

            logger.log('info', 'Done getting list of template apps');

            res.send(appList);

        })
        .catch(err => {
            // Return error msg
            logger.log('error', 'Get templates: ' + err);
            res.send(err);
        })

    next();
}

// Handler for REST endpoint /duplicateNewScript
// URL parameters
//   templateAppId: ID of app to use as template
//   appName: Name of the new app that is created
//   ownerUserId: User ID that should be set as owner of the created app
function respondDuplicateNewScript(req, res, next) {

    // Add owner of new app as header in call to QRS. That way this user will automatically be owner of the newly created app.
    configQRS.headers = { 'X-Qlik-User': 'UserDirectory=' + config.get('senseUserDirectory') + '; UserId=' + req.params.ownerUserId };
    logger.log('debug', configQRS);

    var qrsInteractInstance = new qrsInteract(configQRS);

    // Load script from git
    request.get(loadScriptURL, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            logger.log('verbose', 'Retrieved load script');

            var loadScript = body;
            logger.log('debug', 'Load script: ' + loadScript);

            enigma.getService('qix', configEnigma).then((qix) => {
                const g = qix.global;
                logger.log('debug', req.params.appName + ': Got the global instance');
            });

            var newAppId = '';

            var newOwnerId, newOwnerUserDirectory, newOwnerName;
            var newOwnerUserId = req.params.ownerUserId;

            // Get config data on whether to reload the new app or not
            var reloadNewApp = config.get('reloadNewApp');

            // Make sure the app to be duplicated really is a template
            qrsInteractInstance.Get('app/' + req.params.templateAppId).then(result => {
                logger.log('verbose', req.params.templateAppId + 'Testing if specifiec template app really is a template');

                var appIsTemplate = false;
                result.body.customProperties.forEach(function (item) {
                    logger.log('debug', 'Item: ' + item);

                    if (item.definition.name == 'AppIsTemplate' && item.value == 'Yes') {
                        appIsTemplate = true;
                    }
                })

                logger.log('verbose', req.params.templateAppId + 'App is template: ' + appIsTemplate);

                if (!appIsTemplate) {
                    logger.log('warn', 'The provided app ID does not belong to a template app');
                    next(new restify.InvalidArgumentError("The provided app ID does not belong to a template app."));
                }

                return appIsTemplate;
            })
            .then(result => {
                // result == true if the provided app ID belongs to a template app
                if (result) {
                    qrsInteractInstance.Post('app/' + req.params.templateAppId + '/copy?name=' + req.params.appName, {}, 'json').then(result => {
                        logger.log('info', 'App created with ID %s, using %s as a template ', result.body.id, req.params.templateAppId);
                        newAppId = result.body.id;

                        enigma.getService('qix', configEnigma).then((qix) => {
                            const g = qix.global;

                            // Connect to engine
                            logger.log('verbose', req.params.appName + ': Connecting to engine...');

                            g.openApp(newAppId).then((app) => {
                                logger.log('verbose', 'Setting load script...');
                                app.setScript(loadScript).then((app) => {

                                    // Do a reload of the new app?
                                    if (reloadNewApp) {
                                        logger.log('verbose', req.params.appName + ': Reload app...');
                                        app.doReload();
                                    } else {
                                        logger.log('verbose', req.params.appName + ': App reloading disabled - skipping.');
                                    }

                                    // Close our connection.
                                    logger.log('verbose', req.params.appName + ': Close connection to engine...');
                                    g.session.close().then(() => {
                                        logger.log('info', req.params.appName + ': Done duplicating, new app id=' + newAppId);
                                        var jsonResult = { result: "Done duplicating app", newAppId: newAppId }
                                        res.send(jsonResult);
                                        next();
                                    })
                                    .catch(err => {
                                        // Return error msg
                                        logger.log('error', 'Duplication error 1: ' + err);
                                        next(new restify.BadRequestError("Error occurred when test app template status."));;
                                        return;
                                    })
                                    logger.log('verbose', req.params.appName + ': Connection closed...');
                                })
                                .catch(err => {
                                    // Return error msg
                                    logger.log('error', 'Duplication error 2: ' + err);
                                    next(new restify.BadRequestError("Error occurred when test app template status."));;
                                    return;
                                });
                            })
                            .catch(err => {
                                // Return error msg
                                logger.log('error', 'Duplication error 2: ' + err);
                                next(new restify.BadRequestError("Error occurred when test app template status."));;
                                return;
                            });
                        })
                        .catch(err => {
                            // Return error msg
                            logger.log('error', 'Duplication error 2: ' + err);
                            next(new restify.BadRequestError("Error occurred when test app template status."));;
                            return;
                        });

                    })
                    .catch(err => {
                        // Return error msg
                        logger.log('error', 'Duplication error 3: ' + err);
                        next(new restify.BadRequestError("Error occurred when test app template status."));;
                        return;
                    });
                }
            })
            .catch(err => {
                // Return error msg
                logger.log('error', 'Duplication error 4: ' + err);
                next(new restify.BadRequestError("Error occurred when test app template status."));;
                return;
            });
        }
    })
}

// Handler for REST endpoint /duplicateKeepScript
// URL parameters
//   templateAppId: ID of app to use as template
//   appName: Name of the new app that is created
//   ownerUserId: User ID that should be set as owner of the created app
function respondDuplicateKeepScript(req, res, next) {

    // Add owner of new app as header in call to QRS. That way this user will automatically be owner of the newly created app.
    configQRS.headers = { 'X-Qlik-User': 'UserDirectory=' + config.get('senseUserDirectory') + '; UserId=' + req.params.ownerUserId };
    logger.log('debug', configQRS);

    var qrsInteractInstance = new qrsInteract(configQRS);

    enigma.getService('qix', configEnigma).then((qix) => {
        const g = qix.global;
        logger.log('debug', req.params.appName + ': Got the global instance');
    });

    var newAppId = '';

    var newOwnerId, newOwnerUserDirectory, newOwnerName;
    var newOwnerUserId = req.params.ownerUserId;

    // Get config data on whether to reload the new app or not
    var reloadNewApp = config.get('reloadNewApp');

    // Make sure the app to be duplicated really is a template
    qrsInteractInstance.Get('app/' + req.params.templateAppId)
        .then(result => {
            logger.log('verbose', req.params.templateAppId + 'Testing if specifiec template app really is a template');

            var appIsTemplate = false;
            result.body.customProperties.forEach(function (item) {
                logger.log('debug', 'Item: ' + item);

                if (item.definition.name == 'AppIsTemplate' && item.value == 'Yes') {
                    appIsTemplate = true;
                }
            })

            logger.log('verbose', req.params.templateAppId + 'App is template: ' + appIsTemplate);

            if (!appIsTemplate) {
                logger.log('warn', 'The provided app ID does not belong to a template app');
                next(new restify.InvalidArgumentError("The provided app ID does not belong to a template app."));
            }

            return appIsTemplate;
        })
        .then(result => {
            // result == true if the provided app ID belongs to a template app
            if (result) {
                qrsInteractInstance.Post('app/' + req.params.templateAppId + '/copy?name=' + req.params.appName, {}, 'json').then(result => {
                    logger.log('info', 'App created with ID %s, using %s as a template ', result.body.id, req.params.templateAppId);
                    newAppId = result.body.id;

                    enigma.getService('qix', configEnigma).then((qix) => {
                        const g = qix.global;

                        // Connect to engine
                        logger.log('verbose', req.params.appName + ': Connecting to engine...');

                        g.openApp(newAppId).then((app) => {
                            // Do a reload of the new app?
                            if (reloadNewApp) {
                                logger.log('verbose', req.params.appName + ': Reload app...');
                                app.doReload();
                            } else {
                                logger.log('verbose', req.params.appName + ': App reloading disabled - skipping.');
                            }

                            // Close our connection.
                            logger.log('verbose', req.params.appName + ': Close connection to engine...');
                            g.session.close().then(() => {
                                logger.log('info', req.params.appName + ': Done duplicating, new app id=' + newAppId);
                                var jsonResult = { result: "Done duplicating app", newAppId: newAppId }
                                res.send(jsonResult);
                                next();
                            })
                            .catch(err => {
                                // Return error msg
                                logger.log('error', 'Duplication error 1: ' + err);
                                next(new restify.BadRequestError("Error occurred when test app template status."));;
                                return;
                            })
                            logger.log('verbose', req.params.appName + ': Connection closed...');
                        })
                        .catch(err => {
                            // Return error msg
                            logger.log('error', 'Duplication error 2: ' + err);
                            next(new restify.BadRequestError("Error occurred when test app template status."));;
                            return;
                        });
                    })
                    .catch(err => {
                        // Return error msg
                        logger.log('error', 'Duplication error 3: ' + err);
                        next(new restify.BadRequestError("Error occurred when test app template status."));;
                        return;
                    })
                })
                .catch(err => {
                    // Return error msg
                    logger.log('error', 'Duplication error 4: ' + err);
                    next(new restify.BadRequestError("Error occurred when test app template status."));;
                    return;
                })
            }
        })
        .catch(err => {
            // Return error msg
            logger.log('error', 'Duplication error 5: ' + err);
            // res.send(err);
            next(new restify.BadRequestError("Error occurred when test app template status."));;
            return;
        })

}
<commit_msg>Minor cleanup<commit_after>const enigma = require('enigma.js');
const WebSocket = require('ws');
const fs = require('fs');
const util = require('util')
var qrsInteract = require('qrs-interact');
var request = require('request');
var restify = require('restify');
var winston = require('winston');
var config = require('config');

// Set up Winston logger, logging both to console and different disk files
var logger = new (winston.Logger)({
    transports: [
        new (winston.transports.Console)({
            name: 'console_log',
            'timestamp': true,
            'colorize': true
        }),
        new (winston.transports.File)({
            name: 'file_info',
            filename: config.get('logDirectory') + '/info.log',
            level: 'info'
        }),
        new (winston.transports.File)({
            name: 'file_verbose',
            filename: config.get('logDirectory') + '/verbose.log',
            level: 'verbose'
        }),
        new (winston.transports.File)({
            name: 'file_error',
            filename: config.get('logDirectory') + '/error.log',
            level: 'error'
        })
    ]
});

// Set default log level
logger.transports.console_log.level = config.get('defaultLogLevel');

logger.log('info', 'Starting Qlik Sense template app duplicator.');

// Read certificates
const client = fs.readFileSync(config.get('clientCertPath'));
const client_key = fs.readFileSync(config.get('clientCertKeyPath'));

// Read load script from wherever it is stored (Github etc)
const loadScriptURL = config.get('loadScriptURL');

// Set up enigma.js configuration
const qixSchema = require('enigma.js/schemas/qix/3.2/schema.json');
const configEnigma = {
    schema: qixSchema,
    session: {
        host: config.get('host'),
        port: 4747, // Standard Engine port
        secure: config.get('isSecure'),
        disableCache: true
    },
    createSocket: (url, sessionConfig) => {
        return new WebSocket(url, {
            // ca: rootCert,
            key: client_key,
            cert: client,
            headers: {
                'X-Qlik-User': 'UserDirectory=Internal;UserId=sa_repository'
            },
            rejectUnauthorized: false
        });
    }
}

// Set up Sense repository service configuration
var configQRS = {
    hostname: config.get('host'),
    certificates: {
        certFile: config.get('clientCertPath'),
        keyFile: config.get('clientCertKeyPath'),
    }
}

var restServer = restify.createServer({
    name: 'Qlik Sense app duplicator',
    version: '1.1.0',
    certificate: fs.readFileSync(config.get('sslCertPath')),
    key: fs.readFileSync(config.get('sslCertKeyPath'))
});

// Enable parsing of http parameters
restServer.use(restify.queryParser());

// Set up CORS handling
restServer.use(restify.CORS({ origins: ['*'] }));

// Set up endpoints for REST server
restServer.get('/duplicateNewScript', respondDuplicateNewScript);
restServer.get('/duplicateKeepScript', respondDuplicateKeepScript);
restServer.get('/getTemplateList', respondGetTemplateList);

// Start the server
restServer.listen(8000, function () {
    console.log('%s listening at %s', restServer.name, restServer.url);
});

// Handler for REST endpoint /getTemplateList
// URL parameters
//   -- None --
function respondGetTemplateList(req, res, next) {
    configQRS.headers = { 'X-Qlik-User': 'UserDirectory=Internal; UserId=sa_repository' };
    logger.log('verbose', configQRS);

    var qrsInteractInstance = new qrsInteract(configQRS);

    var appList = [];

    qrsInteractInstance.Get("app/full?filter=@AppIsTemplate eq 'Yes'")
        .then(result => {
            logger.log('debug', 'result=' + result);

            result.body.forEach(function (element) {
                appList.push({
                    name: element.name,
                    id: element.id,
                    description: element.description
                });

                logger.log('verbose', 'Element name: ' + element.name);
                logger.log('verbose', 'App list JSON: ' + JSON.stringify(appList));
            }, this);

            logger.log('info', 'Done getting list of template apps');

            res.send(appList);

        })
        .catch(err => {
            // Return error msg
            logger.log('error', 'Get templates: ' + err);
            res.send(err);
        })

    next();
}

// Handler for REST endpoint /duplicateNewScript
// URL parameters
//   templateAppId: ID of app to use as template
//   appName: Name of the new app that is created
//   ownerUserId: User ID that should be set as owner of the created app
function respondDuplicateNewScript(req, res, next) {

    // Add owner of new app as header in call to QRS. That way this user will automatically be owner of the newly created app.
    configQRS.headers = { 'X-Qlik-User': 'UserDirectory=' + config.get('senseUserDirectory') + '; UserId=' + req.params.ownerUserId };
    logger.log('debug', configQRS);

    var qrsInteractInstance = new qrsInteract(configQRS);

    // Load script from git
    request.get(loadScriptURL, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            logger.log('verbose', 'Retrieved load script');

            var loadScript = body;
            logger.log('debug', 'Load script: ' + loadScript);

            enigma.getService('qix', configEnigma).then((qix) => {
                const g = qix.global;
                logger.log('debug', req.params.appName + ': Got the global instance');
            });

            var newAppId = '';

            var newOwnerId, newOwnerUserDirectory, newOwnerName;
            var newOwnerUserId = req.params.ownerUserId;

            // Get config data on whether to reload the new app or not
            var reloadNewApp = config.get('reloadNewApp');

            // Make sure the app to be duplicated really is a template
            qrsInteractInstance.Get('app/' + req.params.templateAppId).then(result => {
                logger.log('verbose', req.params.templateAppId + 'Testing if specifiec template app really is a template');

                var appIsTemplate = false;
                result.body.customProperties.forEach(function (item) {
                    logger.log('debug', 'Item: ' + item);

                    if (item.definition.name == 'AppIsTemplate' && item.value == 'Yes') {
                        appIsTemplate = true;
                    }
                })

                logger.log('verbose', req.params.templateAppId + 'App is template: ' + appIsTemplate);

                if (!appIsTemplate) {
                    logger.log('warn', 'The provided app ID does not belong to a template app');
                    next(new restify.InvalidArgumentError("The provided app ID does not belong to a template app."));
                }

                return appIsTemplate;
            })
            .then(result => {
                // result == true if the provided app ID belongs to a template app
                if (result) {
                    qrsInteractInstance.Post('app/' + req.params.templateAppId + '/copy?name=' + req.params.appName, {}, 'json').then(result => {
                        logger.log('info', 'App created with ID %s, using %s as a template ', result.body.id, req.params.templateAppId);
                        newAppId = result.body.id;

                        enigma.getService('qix', configEnigma).then((qix) => {
                            const g = qix.global;

                            // Connect to engine
                            logger.log('verbose', req.params.appName + ': Connecting to engine...');

                            g.openApp(newAppId).then((app) => {
                                logger.log('verbose', 'Setting load script...');
                                app.setScript(loadScript).then((app) => {

                                    // Do a reload of the new app?
                                    if (reloadNewApp) {
                                        logger.log('verbose', req.params.appName + ': Reload app...');
                                        app.doReload();
                                    } else {
                                        logger.log('verbose', req.params.appName + ': App reloading disabled - skipping.');
                                    }

                                    // Close our connection.
                                    logger.log('verbose', req.params.appName + ': Close connection to engine...');
                                    g.session.close().then(() => {
                                        logger.log('info', req.params.appName + ': Done duplicating, new app id=' + newAppId);
                                        var jsonResult = { result: "Done duplicating app", newAppId: newAppId }
                                        res.send(jsonResult);
                                        next();
                                    })
                                    .catch(err => {
                                        // Return error msg
                                        logger.log('error', 'Duplication error 1: ' + err);
                                        next(new restify.BadRequestError("Error occurred when test app template status."));;
                                        return;
                                    })
                                    logger.log('verbose', req.params.appName + ': Connection closed...');
                                })
                                .catch(err => {
                                    // Return error msg
                                    logger.log('error', 'Duplication error 2: ' + err);
                                    next(new restify.BadRequestError("Error occurred when test app template status."));;
                                    return;
                                });
                            })
                            .catch(err => {
                                // Return error msg
                                logger.log('error', 'Duplication error 2: ' + err);
                                next(new restify.BadRequestError("Error occurred when test app template status."));;
                                return;
                            });
                        })
                        .catch(err => {
                            // Return error msg
                            logger.log('error', 'Duplication error 2: ' + err);
                            next(new restify.BadRequestError("Error occurred when test app template status."));;
                            return;
                        });

                    })
                    .catch(err => {
                        // Return error msg
                        logger.log('error', 'Duplication error 3: ' + err);
                        next(new restify.BadRequestError("Error occurred when test app template status."));;
                        return;
                    });
                }
            })
            .catch(err => {
                // Return error msg
                logger.log('error', 'Duplication error 4: ' + err);
                next(new restify.BadRequestError("Error occurred when test app template status."));;
                return;
            });
        }
    })
}

// Handler for REST endpoint /duplicateKeepScript
// URL parameters
//   templateAppId: ID of app to use as template
//   appName: Name of the new app that is created
//   ownerUserId: User ID that should be set as owner of the created app
function respondDuplicateKeepScript(req, res, next) {

    // Add owner of new app as header in call to QRS. That way this user will automatically be owner of the newly created app.
    configQRS.headers = { 'X-Qlik-User': 'UserDirectory=' + config.get('senseUserDirectory') + '; UserId=' + req.params.ownerUserId };
    logger.log('debug', configQRS);

    var qrsInteractInstance = new qrsInteract(configQRS);

    enigma.getService('qix', configEnigma).then((qix) => {
        const g = qix.global;
        logger.log('debug', req.params.appName + ': Got the global instance');
    });

    var newAppId = '';

    var newOwnerId, newOwnerUserDirectory, newOwnerName;
    var newOwnerUserId = req.params.ownerUserId;

    // Get config data on whether to reload the new app or not
    var reloadNewApp = config.get('reloadNewApp');

    // Make sure the app to be duplicated really is a template
    qrsInteractInstance.Get('app/' + req.params.templateAppId)
        .then(result => {
            logger.log('verbose', req.params.templateAppId + 'Testing if specifiec template app really is a template');

            var appIsTemplate = false;
            result.body.customProperties.forEach(function (item) {
                logger.log('debug', 'Item: ' + item);

                if (item.definition.name == 'AppIsTemplate' && item.value == 'Yes') {
                    appIsTemplate = true;
                }
            })

            logger.log('verbose', req.params.templateAppId + 'App is template: ' + appIsTemplate);

            if (!appIsTemplate) {
                logger.log('warn', 'The provided app ID does not belong to a template app');
                next(new restify.InvalidArgumentError("The provided app ID does not belong to a template app."));
            }

            return appIsTemplate;
        })
        .then(result => {
            // result == true if the provided app ID belongs to a template app
            if (result) {
                qrsInteractInstance.Post('app/' + req.params.templateAppId + '/copy?name=' + req.params.appName, {}, 'json').then(result => {
                    logger.log('info', 'App created with ID %s, using %s as a template ', result.body.id, req.params.templateAppId);
                    newAppId = result.body.id;

                    enigma.getService('qix', configEnigma).then((qix) => {
                        const g = qix.global;

                        // Connect to engine
                        logger.log('verbose', req.params.appName + ': Connecting to engine...');

                        g.openApp(newAppId).then((app) => {
                            // Do a reload of the new app?
                            if (reloadNewApp) {
                                logger.log('verbose', req.params.appName + ': Reload app...');
                                app.doReload();
                            } else {
                                logger.log('verbose', req.params.appName + ': App reloading disabled - skipping.');
                            }

                            // Close our connection.
                            logger.log('verbose', req.params.appName + ': Close connection to engine...');
                            g.session.close().then(() => {
                                logger.log('info', req.params.appName + ': Done duplicating, new app id=' + newAppId);
                                var jsonResult = { result: "Done duplicating app", newAppId: newAppId }
                                res.send(jsonResult);
                                next();
                            })
                            .catch(err => {
                                // Return error msg
                                logger.log('error', 'Duplication error 1: ' + err);
                                next(new restify.BadRequestError("Error occurred when test app template status."));;
                                return;
                            })
                            logger.log('verbose', req.params.appName + ': Connection closed...');
                        })
                        .catch(err => {
                            // Return error msg
                            logger.log('error', 'Duplication error 2: ' + err);
                            next(new restify.BadRequestError("Error occurred when test app template status."));;
                            return;
                        });
                    })
                    .catch(err => {
                        // Return error msg
                        logger.log('error', 'Duplication error 3: ' + err);
                        next(new restify.BadRequestError("Error occurred when test app template status."));;
                        return;
                    })
                })
                .catch(err => {
                    // Return error msg
                    logger.log('error', 'Duplication error 4: ' + err);
                    next(new restify.BadRequestError("Error occurred when test app template status."));;
                    return;
                })
            }
        })
        .catch(err => {
            // Return error msg
            logger.log('error', 'Duplication error 5: ' + err);
            // res.send(err);
            next(new restify.BadRequestError("Error occurred when test app template status."));;
            return;
        })

}
<|endoftext|>