<commit_before>var fs = require('fs');
var path = require('path');
var async = require('async');
var exec = require('child_process').exec;
var simpleGit = require('simple-git');
var fse = require('fs.extra');

var Tasker = function (site, notify) {
	var me = this;
	me.name = site.name;
	me.tasks = [];

	var emit = function (state, task, details, nolog) {
		console.log(state, JSON.stringify(task));
		task.state = state;
		if (!nolog)
			task.logs.push({state: state, details: details, date: new Date()});
		notify({task: task, site: site, state: state, details: details});
	};

	me.runTask = function (task, cb) {
		me.tasks.push(task);
		emit('start', task);
		async.series([
			function (then) {
				getGit(task, then);
			},
			function (then) {
				runJekyll(task, then);
			},
			function (then) {
				publishSite(task, then);
			}
		], function (err, results) {
			cb();
		});
	};

	var queue = async.queue(me.runTask, 1);

	var getGit = function (task, cb) {
		var branch = site.branch;
		if (task.data.ref) branch = task.data.ref.replace('refs/heads/', '');
		if (site.branch && site.branch !== branch) {
			var err = 'Invalid branch config ' + site.branch + ' !== ' + branch;
			emit('error', task, err);
			return cb(err);
		}
		var git_dir = path.resolve(site.build_path);
		var git = simpleGit(git_dir);
		fs.exists(path.resolve(git_dir, '.git'), function (exists) {
			if (exists) {
				if (err && (err.toString().indexOf('already exists.') < 0)) {
					emit('error', task, err);
					return cb(err);
				}
				emit('updating', task, {path: git_dir, repro: site.repro});
				git.pull('origin', branch, function (err, result) {
					if (err) {
						emit('error', task, err);
						return cb(err);
					}
					emit('updated', task, result);
					cb();
				});
			} else {
				fse.mkdirp(git_dir, function (err) {
					if (!err || (err && err.code === 'EEXIST')) {
						emit('cloning', task, {path: git_dir, repro: site.repro});
						git.clone(site.repro, git_dir, function (err) {
							if (err) {
								emit('error', task, err);
								return cb(err);
							}
							emit('checkout', task, {branch: branch, repro: site.repro});
							git.checkoutBranch(branch, 'origin/' + branch, function (err) {
								if (err && (err.toString().indexOf('already exists.') < 0)) {
									emit('error', task, err);
									return cb(err);
								}
								emit('updating', task, {branch: branch, repro: site.repro});
								git.pull('origin', branch, function (err, result) {
									if (err) {
										emit('error', task, err);
										return cb(err);
									}
									emit('updated', task, result);
									cb(err);
								});
							});
						});
					} else {
						emit('error', task, 'Could not make Build Directory');
						cb(err || 'cannot make dir');
					}
				});
			}
		});
	};

	var buildJekyll = function (task, cb) {
		var jekyll_dir = path.resolve(site.build_path);
		var dest_dir = path.resolve(site.build_path, '_site');
		emit('building', task, {path: jekyll_dir, dest: dest_dir});
		exec('bundle exec jekyll build -d ' + dest_dir, {cwd: jekyll_dir}, function (err, stdout, stderr) {
			var result = '';
			if (stderr && stderr.toString().length > 0) {
				result = result + stderr.toString();
			}
			if (stdout && stdout.toString().length > 0) {
				result = result + stdout.toString();
			}
			if ((result.indexOf('Error') >= 0) ||
				(result.indexOf('command not found') >= 0) ||
				(result.indexOf('Could not locate Gemfile') >= 0) ||
				(result.indexOf('Bundler::GemNotFound') >= 0)
			) {
				if (result.indexOf('Generating... \n') >= 0) {
					result = result.split('Generating... \n')[1];
					if (result.indexOf('done.') >= 0) {
						result = result.split('done.')[0];
					}
				}
				return cb(result);
			}
			if (result.indexOf('Generating... \n') >= 0) {
				result = result.split('Generating... \n')[1];
				if (result.indexOf('done.') >= 0) {
					result = result.split('done.')[0];
				}
			}
			cb(null, result);
		});
	};

	var runJekyll = function (task, cb) {
		buildJekyll(task, function (err, result) {
				if (!err) {
					emit('build', task, result);
					return cb();
				} else if ((err.indexOf('require') >= 0) ||
					(err.indexOf('missing gem executables') >= 0) ||
					(err.indexOf('Could not locate Gemfile') >= 0) ||
					(err.indexOf('GemNotFound') >= 0)
				) {
					var params = '';
					if (err.indexOf('Could not locate Gemfile') >= 0) {
						params = '--gemfile=' + path.resolve(__dirname, '..', 'resource', 'Gemfile') + ' ';
						console.log('using default Gemfile', params);
					}
					var jekyll_dir = path.resolve(site.build_path);
					emit('installing', task, {path: jekyll_dir});
					return exec('bundle ' + params + '--path ' + jekyll_dir + '/.gem', {cwd: jekyll_dir}, function (err, stdout, stderr) {
							if (stderr && stderr.toString().length > 0) {
								err = stderr.toString();
								emit('error', task, err);
								return cb(err);
							}
							var result = stdout.toString();
							if (result.indexOf('Error') >= 0) {
								emit('error', task, result);
								return cb(result);
							}
							emit('installed', task, result);

							buildJekyll(task, function (err, result) {
								if (!err)
									emit('build', task, result);
								else
									emit('error', task, err);
								return cb(err);
							});
						}
					);
				}
				else {
					emit('error', task, err);
					return cb(err);
				}
			}
		);
	};

	var publishSite = function (task, cb) {
		var src_dir = path.resolve(site.build_path, '_site');
		var tmp_dest_dir = path.resolve(site.publish_path + '_backup');
		var dest_dir = path.resolve(site.publish_path);

		var backupPublishFolder = function (then) {
			fse.exists(dest_dir, function (exists) {
				if (!exists) return then();
				emit('backing up', task);
				fse.move(dest_dir, tmp_dest_dir, function (err) {
					if (!err || (err && err.code === 'ENOENT')) {
						then();
					} else {
						emit('error', task, 'Failed backup publish folder');
						then(err);
					}
				});
			});
		};

		var removeBackupFolder = function (then) {
			fse.exists(tmp_dest_dir, function (exists) {
				if (!exists) return then();
				emit('removing backup', task);
				fse.rmrf(tmp_dest_dir, function (err) {
					if (!err || (err && err.code === 'ENOENT')) {
						then();
					} else {
						emit('error', task, 'Failed remove backup folder');
						then(err);
					}
				});
			});
		};

		var restorePublishFolder = function (then) {
			fse.exists(tmp_dest_dir, function (exists) {
				if (!exists) return then();
				emit('restoring backup', task);
				fse.move(tmp_dest_dir, dest_dir, function (err) {
					if (!err || (err && err.code === 'ENOENT')) {
						then();
					} else {
						emit('error', task, 'Failed restoring backup folder');
						then(err);
					}
				});
			});
		};

		var movePublishFolder = function (then) {
			emit('publishing', task);
			fse.move(src_dir, dest_dir, function (err) {
				if (!err) {
					then();
				} else {
					emit('error', task, 'Failed moving publish folder');
					then(err);
				}
			});
		};

		backupPublishFolder(function (err) {
			if (err) return cb(err);
			movePublishFolder(function (err) {
				if (err) {
					restorePublishFolder(function (err2) {
						cb(err || err2);
					});
				} else {
					removeBackupFolder(function (err2) {
						emit('published', task);
						cb(err2);
					});
				}
			});
		});
	};

	me.state = function () {
		if (me.tasks.length) return me.tasks[me.tasks.length - 1].state;
		return 'idle';
	};

	me.details = function () {
		return me.tasks[me.tasks.length - 1];
	};

	me.exec = function (task) {
		task.logs = [];
		emit('queued', task, null, true);
		queue.push(task);
	};
};

var Executer = function (config, notify) {
	var me = this;

	me.tasks = [];
	config.sites.forEach(function (site) {
		me.tasks.push(new Tasker(site, notify));
	});

	me.states = function () {
		var result = {};
		me.tasks.forEach(function (tasker) {
			result[tasker.name] = tasker.state();
		});
		return result;
	};

	me.details = function (name) {
		var tasker = me.tasks.filter(function (t) {
			return name === t.name;
		})[0];
		if (tasker) {
			return tasker.details();
		}
		return null;
	};

	me.exec = function (task) {
		var tasker = me.tasks.filter(function (t) {
			return t.name === task.name;
		})[0];
		if (tasker) tasker.exec(task);
	};
};

module.exports = Executer;
<commit_msg>use default gemfile if not in git<commit_after>var fs = require('fs');
var path = require('path');
var async = require('async');
var exec = require('child_process').exec;
var simpleGit = require('simple-git');
var fse = require('fs.extra');

var Tasker = function (site, notify) {
	var me = this;
	me.name = site.name;
	me.tasks = [];

	var emit = function (state, task, details, nolog) {
		//console.log(state, JSON.stringify(task));
		task.state = state;
		if (!nolog)
			task.logs.push({state: state, details: details, date: new Date()});
		notify({task: task, site: site, state: state, details: details});
	};

	me.runTask = function (task, cb) {
		me.tasks.push(task);
		emit('start', task);
		async.series([
			function (then) {
				getGit(task, then);
			},
			function (then) {
				runJekyll(task, then);
			},
			function (then) {
				publishSite(task, then);
			}
		], function (err, results) {
			cb();
		});
	};

	var queue = async.queue(me.runTask, 1);

	var getGit = function (task, cb) {
		var branch = site.branch;
		if (task.data.ref) branch = task.data.ref.replace('refs/heads/', '');
		if (site.branch && site.branch !== branch) {
			var err = 'Invalid branch config ' + site.branch + ' !== ' + branch;
			emit('error', task, err);
			return cb(err);
		}
		var git_dir = path.resolve(site.build_path);
		var git = simpleGit(git_dir);
		fs.exists(path.resolve(git_dir, '.git'), function (exists) {
			if (exists) {
				if (err && (err.toString().indexOf('already exists.') < 0)) {
					emit('error', task, err);
					return cb(err);
				}
				emit('updating', task, {path: git_dir, repro: site.repro});
				git.pull('origin', branch, function (err, result) {
					if (err) {
						emit('error', task, err);
						return cb(err);
					}
					emit('updated', task, result);
					cb();
				});
			} else {
				fse.mkdirp(git_dir, function (err) {
					if (!err || (err && err.code === 'EEXIST')) {
						emit('cloning', task, {path: git_dir, repro: site.repro});
						git.clone(site.repro, git_dir, function (err) {
							if (err) {
								emit('error', task, err);
								return cb(err);
							}
							emit('checkout', task, {branch: branch, repro: site.repro});
							git.checkoutBranch(branch, 'origin/' + branch, function (err) {
								if (err && (err.toString().indexOf('already exists.') < 0)) {
									emit('error', task, err);
									return cb(err);
								}
								emit('updating', task, {branch: branch, repro: site.repro});
								git.pull('origin', branch, function (err, result) {
									if (err) {
										emit('error', task, err);
										return cb(err);
									}
									emit('updated', task, result);
									cb(err);
								});
							});
						});
					} else {
						emit('error', task, 'Could not make Build Directory');
						cb(err || 'cannot make dir');
					}
				});
			}
		});
	};

	var buildJekyll = function (task, cb) {
		var jekyll_dir = path.resolve(site.build_path);
		var dest_dir = path.resolve(site.build_path, '_site');
		emit('building', task, {path: jekyll_dir, dest: dest_dir});
		var params = '';
		if (fs.existsSync(path.resolve(jekyll_dir, 'Gemfile'))) {
			params = '--gemfile=' + path.resolve(__dirname, '..', 'resource', 'Gemfile') + ' ';
		}
		exec('bundle ' + params + 'exec jekyll build -d ' + dest_dir, {cwd: jekyll_dir}, function (err, stdout, stderr) {
			var result = '';
			if (stderr && stderr.toString().length > 0) {
				result = result + stderr.toString();
			}
			if (stdout && stdout.toString().length > 0) {
				result = result + stdout.toString();
			}
			if ((result.indexOf('Error') >= 0) ||
				(result.indexOf('command not found') >= 0) ||
				(result.indexOf('Could not locate Gemfile') >= 0) ||
				(result.indexOf('Bundler::GemNotFound') >= 0)
			) {
				if (result.indexOf('Generating... \n') >= 0) {
					result = result.split('Generating... \n')[1];
					if (result.indexOf('done.') >= 0) {
						result = result.split('done.')[0];
					}
				}
				return cb(result);
			}
			if (result.indexOf('Generating... \n') >= 0) {
				result = result.split('Generating... \n')[1];
				if (result.indexOf('done.') >= 0) {
					result = result.split('done.')[0];
				}
			}
			cb(null, result);
		});
	};

	var runJekyll = function (task, cb) {
		buildJekyll(task, function (err, result) {
				if (!err) {
					emit('build', task, result);
					return cb();
				} else if ((err.indexOf('require') >= 0) ||
					(err.indexOf('missing gem executables') >= 0) ||
					(err.indexOf('Could not locate Gemfile') >= 0) ||
					(err.indexOf('GemNotFound') >= 0)
				) {
					var params = '';
					if (err.indexOf('Could not locate Gemfile') >= 0) {
						params = '--gemfile=' + path.resolve(__dirname, '..', 'resource', 'Gemfile') + ' ';
						//console.log('using default Gemfile', params);
					}
					var jekyll_dir = path.resolve(site.build_path);
					emit('installing', task, {path: jekyll_dir});
					return exec('bundle ' + params + '--path ' + jekyll_dir + '/.gem', {cwd: jekyll_dir}, function (err, stdout, stderr) {
							if (stderr && stderr.toString().length > 0) {
								err = stderr.toString();
								emit('error', task, err);
								return cb(err);
							}
							var result = stdout.toString();
							if (result.indexOf('Error') >= 0) {
								emit('error', task, result);
								return cb(result);
							}
							emit('installed', task, result);

							buildJekyll(task, function (err, result) {
								if (!err)
									emit('build', task, result);
								else
									emit('error', task, err);
								return cb(err);
							});
						}
					);
				}
				else {
					emit('error', task, err);
					return cb(err);
				}
			}
		);
	};

	var publishSite = function (task, cb) {
		var src_dir = path.resolve(site.build_path, '_site');
		var tmp_dest_dir = path.resolve(site.publish_path + '_backup');
		var dest_dir = path.resolve(site.publish_path);

		var backupPublishFolder = function (then) {
			fse.exists(dest_dir, function (exists) {
				if (!exists) return then();
				emit('backing up', task);
				fse.move(dest_dir, tmp_dest_dir, function (err) {
					if (!err || (err && err.code === 'ENOENT')) {
						then();
					} else {
						emit('error', task, 'Failed backup publish folder');
						then(err);
					}
				});
			});
		};

		var removeBackupFolder = function (then) {
			fse.exists(tmp_dest_dir, function (exists) {
				if (!exists) return then();
				emit('removing backup', task);
				fse.rmrf(tmp_dest_dir, function (err) {
					if (!err || (err && err.code === 'ENOENT')) {
						then();
					} else {
						emit('error', task, 'Failed remove backup folder');
						then(err);
					}
				});
			});
		};

		var restorePublishFolder = function (then) {
			fse.exists(tmp_dest_dir, function (exists) {
				if (!exists) return then();
				emit('restoring backup', task);
				fse.move(tmp_dest_dir, dest_dir, function (err) {
					if (!err || (err && err.code === 'ENOENT')) {
						then();
					} else {
						emit('error', task, 'Failed restoring backup folder');
						then(err);
					}
				});
			});
		};

		var movePublishFolder = function (then) {
			emit('publishing', task);
			fse.move(src_dir, dest_dir, function (err) {
				if (!err) {
					then();
				} else {
					emit('error', task, 'Failed moving publish folder');
					then(err);
				}
			});
		};

		backupPublishFolder(function (err) {
			if (err) return cb(err);
			movePublishFolder(function (err) {
				if (err) {
					restorePublishFolder(function (err2) {
						cb(err || err2);
					});
				} else {
					removeBackupFolder(function (err2) {
						emit('published', task);
						cb(err2);
					});
				}
			});
		});
	};

	me.state = function () {
		if (me.tasks.length) return me.tasks[me.tasks.length - 1].state;
		return 'idle';
	};

	me.details = function () {
		return me.tasks[me.tasks.length - 1];
	};

	me.exec = function (task) {
		task.logs = [];
		emit('queued', task, null, true);
		queue.push(task);
	};
};

var Executer = function (config, notify) {
	var me = this;

	me.tasks = [];
	config.sites.forEach(function (site) {
		me.tasks.push(new Tasker(site, notify));
	});

	me.states = function () {
		var result = {};
		me.tasks.forEach(function (tasker) {
			result[tasker.name] = tasker.state();
		});
		return result;
	};

	me.details = function (name) {
		var tasker = me.tasks.filter(function (t) {
			return name === t.name;
		})[0];
		if (tasker) {
			return tasker.details();
		}
		return null;
	};

	me.exec = function (task) {
		var tasker = me.tasks.filter(function (t) {
			return t.name === task.name;
		})[0];
		if (tasker) tasker.exec(task);
	};
};

module.exports = Executer;
<|endoftext|>


<commit_before>// check for Geolocation support
if (navigator.geolocation)
{

    console.log('Geolocation is supported!');
}
else
{
    alert('Geolocation is not supported for this Browser/OS version yet.');
}

// our current position
var worldAdjustRotation = null;
var positionHng;
var defaultOrientation;
var positionCurrent = {
    lat: null,
    lng: null,
    hng: null
};

function getBrowserOrientation() {
    var orientation;
    if (screen.orientation && screen.orientation.type) {
        orientation = screen.orientation.type;
    } else {
        orientation = screen.orientation ||
            screen.mozOrientation ||
            screen.msOrientation;
    }

    /*
     'portait-primary':      for (screen width < screen height, e.g. phone, phablet, small tablet)
     device is in 'normal' orientation
     for (screen width > screen height, e.g. large tablet, laptop)
     device has been turned 90deg clockwise from normal

     'portait-secondary':    for (screen width < screen height)
     device has been turned 180deg from normal
     for (screen width > screen height)
     device has been turned 90deg anti-clockwise (or 270deg clockwise) from normal

     'landscape-primary':    for (screen width < screen height)
     device has been turned 90deg clockwise from normal
     for (screen width > screen height)
     device is in 'normal' orientation

     'landscape-secondary':  for (screen width < screen height)
     device has been turned 90deg anti-clockwise (or 270deg clockwise) from normal
     for (screen width > screen height)
     device has been turned 180deg from normal
     */

    return orientation;
}

function onHeadingChange(event) {
    var heading = event.alpha;

    if (typeof event.webkitCompassHeading !== "undefined") {
        heading = event.webkitCompassHeading; //iOS non-standard
    }

    var orientation = getBrowserOrientation();

    if (typeof heading !== "undefined" && heading !== null)
    {
        // && typeof orientation !== "undefined") {
        // we have a browser that reports device heading and orientation

        // what adjustment we have to add to rotation to allow for current device orientation
        var adjustment = 0;
        if (defaultOrientation === "landscape") {
            adjustment -= 90;
        }

        if (typeof orientation !== "undefined") {
            var currentOrientation = orientation.split("-");

            if (defaultOrientation !== currentOrientation[0])
            {
                if (defaultOrientation === "landscape")
                {
                    adjustment -= 270;
                }
                else
                {
                    adjustment -= 90;
                }
            }

            if (currentOrientation[1] === "secondary")
            {
                adjustment -= 180;
            }
        }

        positionCurrent.hng = heading + adjustment;

        var phase = positionCurrent.hng < 0 ? 360 + positionCurrent.hng : positionCurrent.hng;
        //positionHng.textContent = (360 - phase | 0) + "°";

        console.log('positionCurrent.hng ',positionCurrent.hng,positionCurrent);
        console.log( (360 - phase | 0) + "°");

        if(worldAdjustRotation==null != worldAdjustRotation !=0)
        {
            worldAdjustRotation = positionCurrent.hng;
            $('#pointers').attr('rotation','0 '+positionCurrent.hng+' 0');
            alert('Ajuste: '+parseInt(positionCurrent.hng));
        }

        $('.brujula').html(parseFloat(positionCurrent.hng).toFixed(2)+ "°")

        /*
        // apply rotation to compass rose
        if (typeof rose.style.transform !== "undefined") {
            rose.style.transform = "rotateZ(" + positionCurrent.hng + "deg)";
        } else if (typeof rose.style.webkitTransform !== "undefined") {
            rose.style.webkitTransform = "rotateZ(" + positionCurrent.hng + "deg)";
        }
        */
    }
    else
    {
        // device can't show heading

        positionHng  = "n/a";
        console.log('brujula no disponible');
    }
}

if (window.DeviceOrientationEvent) {

    if (screen.width > screen.height)
    {
        defaultOrientation = "landscape";
    }
    else
    {
        defaultOrientation = "portrait";
    }

    window.addEventListener("deviceorientation", onHeadingChange);
    /*
    window.addEventListener("deviceorientation", function(event)
    {

        var xValue      = Math.round(event.gamma);
        var yValue      = Math.round(event.beta);
        var rotation    = Math.round(event.alpha);

        //$('#cameraControl').attr('rotation' , '0 '+yValue+' 0');

        $('.orientation').html('xValue :'+xValue + '
yValue :'+yValue+ '
rotation :'+rotation)

    }, true)
    */

} else {
    alert("Sorry, your browser doesn't support Device Orientation");
}

MAP_WIDTH           = 1000000;
MAP_HEIGHT          = 1000000;
var store_current   = null;

function convert(lat, lon){
    var y = ((-1 * lat) + 90) * (MAP_HEIGHT / 180);
    var x = (lon + 180) * (MAP_WIDTH / 360);
    return {x:x,y:y};
}

window.onload = function()
{
    var startPos;
    var stores =
    [
        {
            lat: 19.4375921,
            lng: -99.2099433,
            info:{
                title: 'Superama Los Morales',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'superama'
            }
        },
        {
            lat: 19.4477107,
            lng: -99.2171927,
            info:{
                title: 'Sam\'s Club Toreo',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'sams'
            }
        },
        {
            lat: 19.4373714,
            lng: -99.2056145,
            info:{
                title: 'Superama Polanco',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'superama'
            }
        },
        {
            lat: 19.2967949,
            lng: -99.1061447,
            info:{
                title: 'Walmart Pabellón Cuemanco',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'supercenter'
            }
        },
        {
            lat: 19.2995844,
            lng: -99.1070249,
            info:{
                title: 'Bodega Aurrera Hueso',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'ba'
            }
        },
        {
            lat: 19.2998148,
            lng: -99.1137976,
            info:{
                title: 'Audi Coapa',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'ba'
            }
        },
        {
            lat: 19.2996258,
            lng: -99.1115139,
            info:{
                title: 'Biker Coapa',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'ba'
            }
        },
        {
            lat:19.299052,
            lng: -99.1116174,
            info:{
                title: 'IIM',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'ba'
            }
        },

    ];

    $('.cerrar').click(function()
    {
        $('.info').hide();
    });

    $('.debug').click(function()
    {
        if($('#tripmeter').is(':visible'))
        {
            $('#tripmeter').hide();
        }
        else
        {
            $('#tripmeter').show();
        }
    });

    navigator.geolocation.getCurrentPosition(
        function(position)
        {
            startPos = position;
            document.getElementById('startLat').innerHTML = startPos.coords.latitude;
            document.getElementById('startLon').innerHTML = startPos.coords.longitude;

            var convStart   = convert(startPos.coords.latitude,startPos.coords.longitude);
            var conv        = convert(stores[0].lat,stores[0].lng);
            var dif         =
            {
                x: convStart.x - conv.x,
                y: convStart.y - conv.y
            }

            $('#superama').attr('position',dif.x+' 0.5 '+dif.y);

            var convSams    = convert(stores[1].lat,stores[1].lng);
            var difSams     =
            {
                x: convStart.x - convSams.x,
                y: convStart.y - convSams.y
            }

            $('#sams').attr('position',difSams.x+' 0.5 '+difSams.y);
            $('#sams').attr('show-info', JSON.stringify(stores[1].info));

            var convSup    = convert(stores[2].lat,stores[2].lng);
            var difSup     =
            {
                x: convStart.x - convSup.x,
                y: convStart.y - convSup.y
            }
            $('#supPol').attr('position',difSup.x+' 0.5 '+difSup.y);

            var offset = 10000;
            var difCoordenads = {
                x: (startPos.coords.latitude     - stores[0].lat)*offset,
                y: (startPos.coords.longitude    - stores[0].lng)*offset
            }

            $('#sams').attr('position',difCoordenads.x+' 0.5 '+difCoordenads.y);
            console.log(dif,difCoordenads)

            generatePin(startPos)

        },
        function(error)
        {
            alert('Error occurred. Error code: ' + error.code);
            // error.code can be:
            //   0: unknown error
            //   1: permission denied
            //   2: position unavailable (error response from locaton provider)
            //   3: timed out
        });

    navigator.geolocation.watchPosition(
        function(position)
        {
            console.log('store_current ',store_current);
            document.getElementById('currentLat').innerHTML = position.coords.latitude;
            document.getElementById('currentLon').innerHTML = position.coords.longitude;

            $('.stores').html('');
            for(var s in stores)
            {
                $('.stores').append('
Distance to '+stores[s].info.title+':
 '+calculateDistance(stores[s].lat, stores[s].lng,position.coords.latitude, position.coords.longitude)+' m
')
            }

            if(store_current!=null)
            {
                $('.txt.dist').show();
                $('.txt.dist').html(this.calculateDistance(store_current.lat, store_current.lng,position.coords.latitude, position.coords.longitude)+'m');
            }

        });

    function generatePin(startPos)
    {
        var offset  = 10000;
        var scene   = document.getElementById('pointers');

        for(var s in stores)
        {

            var convStart   = convert(startPos.coords.latitude,startPos.coords.longitude);
            var conv        = convert(stores[s].lat,stores[s].lng);
            var difCoordenads =
            {
                x: convStart.x - conv.x,
                y: convStart.y - conv.y
            }

            difCoordenads =
            {
                x: (startPos.coords.latitude     - stores[s].lat)*offset,
                y: (startPos.coords.longitude    - stores[s].lng)*offset
            }

            var box	= document.createElement('a-box');
            box.setAttribute("position"	, difCoordenads.x+' 0.5 '+difCoordenads.y);
            box.setAttribute("rotation"	, "0 45 0");
            box.setAttribute("show-info", JSON.stringify(stores[s]));
            box.setAttribute("color"    , "#123123");
            //box.setAttribute("look-at"	, "[camera]");

            console.log('position' , difCoordenads.x+' 0.5 '+difCoordenads.y)

            scene.appendChild(box);
        }

        var cylinder	= document.createElement('a-cylinder');
        cylinder.setAttribute("position"    , '0 0 -10');
        cylinder.setAttribute("radius"	    , "0.5");
        cylinder.setAttribute("height"      , "1.5");
        cylinder.setAttribute("color"       , "#ff0000");

        scene.appendChild(cylinder);
    }
};

function calculateDistance(lat1, lon1, lat2, lon2)
{
    var R = 6371 * 1000; // m
    var dLat = (lat2 - lat1).toRad();
    var dLon = (lon2 - lon1).toRad();
    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
            Math.sin(dLon / 2) * Math.sin(dLon / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = R * c;
    return d;
}

Number.prototype.toRad = function()
{
    return this * Math.PI / 180;
}
<commit_msg>compass<commit_after>// check for Geolocation support
if (navigator.geolocation)
{

    console.log('Geolocation is supported!');
}
else
{
    alert('Geolocation is not supported for this Browser/OS version yet.');
}

// our current position
var worldAdjustRotation = null;
var positionHng;
var defaultOrientation;
var positionCurrent = {
    lat: null,
    lng: null,
    hng: null
};

function getBrowserOrientation() {
    var orientation;
    if (screen.orientation && screen.orientation.type) {
        orientation = screen.orientation.type;
    } else {
        orientation = screen.orientation ||
            screen.mozOrientation ||
            screen.msOrientation;
    }

    /*
     'portait-primary':      for (screen width < screen height, e.g. phone, phablet, small tablet)
     device is in 'normal' orientation
     for (screen width > screen height, e.g. large tablet, laptop)
     device has been turned 90deg clockwise from normal

     'portait-secondary':    for (screen width < screen height)
     device has been turned 180deg from normal
     for (screen width > screen height)
     device has been turned 90deg anti-clockwise (or 270deg clockwise) from normal

     'landscape-primary':    for (screen width < screen height)
     device has been turned 90deg clockwise from normal
     for (screen width > screen height)
     device is in 'normal' orientation

     'landscape-secondary':  for (screen width < screen height)
     device has been turned 90deg anti-clockwise (or 270deg clockwise) from normal
     for (screen width > screen height)
     device has been turned 180deg from normal
     */

    return orientation;
}

function onHeadingChange(event) {
    var heading = event.alpha;

    if (typeof event.webkitCompassHeading !== "undefined") {
        heading = event.webkitCompassHeading; //iOS non-standard
    }

    var orientation = getBrowserOrientation();

    if (typeof heading !== "undefined" && heading !== null)
    {
        // && typeof orientation !== "undefined") {
        // we have a browser that reports device heading and orientation

        // what adjustment we have to add to rotation to allow for current device orientation
        var adjustment = 0;
        if (defaultOrientation === "landscape") {
            adjustment -= 90;
        }

        if (typeof orientation !== "undefined") {
            var currentOrientation = orientation.split("-");

            if (defaultOrientation !== currentOrientation[0])
            {
                if (defaultOrientation === "landscape")
                {
                    adjustment -= 270;
                }
                else
                {
                    adjustment -= 90;
                }
            }

            if (currentOrientation[1] === "secondary")
            {
                adjustment -= 180;
            }
        }

        positionCurrent.hng = heading + adjustment;

        var phase = positionCurrent.hng < 0 ? 360 + positionCurrent.hng : positionCurrent.hng;
        //positionHng.textContent = (360 - phase | 0) + "°";

        console.log('positionCurrent.hng ',positionCurrent.hng,positionCurrent);
        console.log( (360 - phase | 0) + "°");

        /*
        if(worldAdjustRotation==null != worldAdjustRotation !=0)
        {
            worldAdjustRotation = positionCurrent.hng;
            $('#pointers').attr('rotation','0 '+positionCurrent.hng+' 0');
            alert('Ajuste: '+parseInt(positionCurrent.hng));
        }
        */
        $('#pointers').attr('rotation','0 -65 0');

        $('.brujula').html(parseFloat(positionCurrent.hng).toFixed(2)+ "°")

        /*
        // apply rotation to compass rose
        if (typeof rose.style.transform !== "undefined") {
            rose.style.transform = "rotateZ(" + positionCurrent.hng + "deg)";
        } else if (typeof rose.style.webkitTransform !== "undefined") {
            rose.style.webkitTransform = "rotateZ(" + positionCurrent.hng + "deg)";
        }
        */
    }
    else
    {
        // device can't show heading

        positionHng  = "n/a";
        console.log('brujula no disponible');
    }
}

if (window.DeviceOrientationEvent) {

    if (screen.width > screen.height)
    {
        defaultOrientation = "landscape";
    }
    else
    {
        defaultOrientation = "portrait";
    }

    window.addEventListener("deviceorientation", onHeadingChange);
    /*
    window.addEventListener("deviceorientation", function(event)
    {

        var xValue      = Math.round(event.gamma);
        var yValue      = Math.round(event.beta);
        var rotation    = Math.round(event.alpha);

        //$('#cameraControl').attr('rotation' , '0 '+yValue+' 0');

        $('.orientation').html('xValue :'+xValue + '
yValue :'+yValue+ '
rotation :'+rotation)

    }, true)
    */

} else {
    alert("Sorry, your browser doesn't support Device Orientation");
}

MAP_WIDTH           = 1000000;
MAP_HEIGHT          = 1000000;
var store_current   = null;

function convert(lat, lon){
    var y = ((-1 * lat) + 90) * (MAP_HEIGHT / 180);
    var x = (lon + 180) * (MAP_WIDTH / 360);
    return {x:x,y:y};
}

window.onload = function()
{
    var startPos;
    var stores =
    [
        {
            lat: 19.4375921,
            lng: -99.2099433,
            info:{
                title: 'Superama Los Morales',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'superama'
            }
        },
        {
            lat: 19.4477107,
            lng: -99.2171927,
            info:{
                title: 'Sam\'s Club Toreo',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'sams'
            }
        },
        {
            lat: 19.4373714,
            lng: -99.2056145,
            info:{
                title: 'Superama Polanco',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'superama'
            }
        },
        {
            lat: 19.2967949,
            lng: -99.1061447,
            info:{
                title: 'Walmart Pabellón Cuemanco',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'supercenter'
            }
        },
        {
            lat: 19.2995844,
            lng: -99.1070249,
            info:{
                title: 'Bodega Aurrera Hueso',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'ba'
            }
        },
        {
            lat: 19.2998148,
            lng: -99.1137976,
            info:{
                title: 'Audi Coapa',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'ba'
            }
        },
        {
            lat: 19.2996258,
            lng: -99.1115139,
            info:{
                title: 'Biker Coapa',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'ba'
            }
        },
        {
            lat:19.299052,
            lng: -99.1116174,
            info:{
                title: 'IIM',
                horario: 'horario',
                promo: 'promo',
                info: 'info',
                desc: 'desc',
                dist: 'dist',
                type: 'ba'
            }
        },

    ];

    $('.cerrar').click(function()
    {
        $('.info').hide();
    });

    $('.debug').click(function()
    {
        if($('#tripmeter').is(':visible'))
        {
            $('#tripmeter').hide();
        }
        else
        {
            $('#tripmeter').show();
        }
    });

    navigator.geolocation.getCurrentPosition(
        function(position)
        {
            startPos = position;
            document.getElementById('startLat').innerHTML = startPos.coords.latitude;
            document.getElementById('startLon').innerHTML = startPos.coords.longitude;

            var convStart   = convert(startPos.coords.latitude,startPos.coords.longitude);
            var conv        = convert(stores[0].lat,stores[0].lng);
            var dif         =
            {
                x: convStart.x - conv.x,
                y: convStart.y - conv.y
            }

            $('#superama').attr('position',dif.x+' 0.5 '+dif.y);

            var convSams    = convert(stores[1].lat,stores[1].lng);
            var difSams     =
            {
                x: convStart.x - convSams.x,
                y: convStart.y - convSams.y
            }

            $('#sams').attr('position',difSams.x+' 0.5 '+difSams.y);
            $('#sams').attr('show-info', JSON.stringify(stores[1].info));

            var convSup    = convert(stores[2].lat,stores[2].lng);
            var difSup     =
            {
                x: convStart.x - convSup.x,
                y: convStart.y - convSup.y
            }
            $('#supPol').attr('position',difSup.x+' 0.5 '+difSup.y);

            var offset = 10000;
            var difCoordenads = {
                x: (startPos.coords.latitude     - stores[0].lat)*offset,
                y: (startPos.coords.longitude    - stores[0].lng)*offset
            }

            $('#sams').attr('position',difCoordenads.x+' 0.5 '+difCoordenads.y);
            console.log(dif,difCoordenads)

            generatePin(startPos)

        },
        function(error)
        {
            alert('Error occurred. Error code: ' + error.code);
            // error.code can be:
            //   0: unknown error
            //   1: permission denied
            //   2: position unavailable (error response from locaton provider)
            //   3: timed out
        });

    navigator.geolocation.watchPosition(
        function(position)
        {
            console.log('store_current ',store_current);
            document.getElementById('currentLat').innerHTML = position.coords.latitude;
            document.getElementById('currentLon').innerHTML = position.coords.longitude;

            $('.stores').html('');
            for(var s in stores)
            {
                $('.stores').append('
Distance to '+stores[s].info.title+':
 '+calculateDistance(stores[s].lat, stores[s].lng,position.coords.latitude, position.coords.longitude)+' m
')
            }

            if(store_current!=null)
            {
                $('.txt.dist').show();
                $('.txt.dist').html(this.calculateDistance(store_current.lat, store_current.lng,position.coords.latitude, position.coords.longitude)+'m');
            }

        });

    function generatePin(startPos)
    {
        var offset  = 10000;
        var scene   = document.getElementById('pointers');

        for(var s in stores)
        {

            var convStart   = convert(startPos.coords.latitude,startPos.coords.longitude);
            var conv        = convert(stores[s].lat,stores[s].lng);
            var difCoordenads =
            {
                x: convStart.x - conv.x,
                y: convStart.y - conv.y
            }

            difCoordenads =
            {
                x: (startPos.coords.latitude     - stores[s].lat)*offset,
                y: (startPos.coords.longitude    - stores[s].lng)*offset
            }

            var box	= document.createElement('a-box');
            box.setAttribute("position"	, difCoordenads.x+' 0.5 '+difCoordenads.y);
            box.setAttribute("rotation"	, "0 45 0");
            box.setAttribute("show-info", JSON.stringify(stores[s]));
            box.setAttribute("color"    , "#123123");
            //box.setAttribute("look-at"	, "[camera]");

            console.log('position' , difCoordenads.x+' 0.5 '+difCoordenads.y)

            scene.appendChild(box);
        }

        var cylinder	= document.createElement('a-cylinder');
        cylinder.setAttribute("position"    , '0 0 -10');
        cylinder.setAttribute("radius"	    , "0.5");
        cylinder.setAttribute("height"      , "1.5");
        cylinder.setAttribute("color"       , "#ff0000");

        scene.appendChild(cylinder);
    }
};

function calculateDistance(lat1, lon1, lat2, lon2)
{
    var R = 6371 * 1000; // m
    var dLat = (lat2 - lat1).toRad();
    var dLon = (lon2 - lon1).toRad();
    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
            Math.sin(dLon / 2) * Math.sin(dLon / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = R * c;
    return d;
}

Number.prototype.toRad = function()
{
    return this * Math.PI / 180;
}
<|endoftext|>


<commit_before>var Device = require('./device');
var Errors = require('./errors');

var fs   = require('fs');
var q    = require('q');
var tls  = require('tls');
var util = require('./util');
var debug = function() {};
if(process.env.DEBUG) {
	try {
		debug = require('debug')('apnfb');
	}
	catch (e) {
		console.log("Notice: 'debug' module is not available. This should be installed with `npm install debug` to enable debug messages", e);
		debug = function() {};
	}
}
/**
 * Create a new connection to the APN Feedback.
 * @constructor
 * @param {Object} [options]
 * @config {String} [cert="cert.pem"] The filename of the connection certificate to load from disk
 * @config {Buffer|String} [certData] The certificate data. If supplied, will be used instead of loading from disk.
 * @config {String} [key="key.pem"] The filename of the connection key to load from disk
 * @config {Buffer|String} [keyData] The key data. If supplied will be used instead of loading from disk.
 * @config {String} [passphrase] The passphrase for the connection key, if required
 * @config {Buffer[]|String[]} [ca] An array of strings or Buffers of trusted certificates. If this is omitted several well known "root" CAs will be used, like VeriSign. - You may need to use this as some environments don't include the CA used by Apple
 * @config {String} [address="feedback.push.apple.com"] The feedback server to connect to.
 * @config {Number} [port=2195] Feedback server port
 * @config {Function} [feedback] A callback which accepts 2 parameters (timestamp, {@link Device}). See: {@link Communicating with APS.
 * @config {Function} [errorCallback] Callback which will capture connection errors
 * @config {Number} [interval=3600] Interval to automatically connect to the Feedback service.
 */
function Feedback(options) {

	this.options = {
		cert: 'cert.pem',					/* Certificate file */
		certData: null,						/* Certificate data */
		key: 'key.pem',						/* Key file */
		keyData: null,						/* Key data */
		passphrase: null,                   /* Passphrase for key */
		ca: null,							/* Certificate Authority
		address: 'feedback.push.apple.com',	/* feedback address */
		port: 2196,							/* feedback port */
		feedback: false,					/* enable feedback service, set to callback */
		errorCallback: false,				/* error handler to catch connection exceptions */
		interval: 3600,						/* interval in seconds to connect to feedback service */
	};

	util.extend(this.options, options);

	this.certData = null;
	this.keyData = null;

	this.deferredInitialize = null;
	this.deferredConnection = null;

	this.readBuffer = null;
	this.interval = null;

	if (typeof this.options.feedback != 'function') {
		throw new Error(-1, 'A callback function must be specified');
	}

	this.start();
};

/**
 * @private
 */
Feedback.prototype.checkInitialized = function () {
	if (this.keyData && this.certData) {
		this.deferredInitialize.resolve();
	}
};

/**
 * @private
 */
Feedback.prototype.initialize = function () {
	if (this.deferredInitialize) {
		return this.deferredInitialize.promise;
	}

	debug("Initialising module");
	this.deferredInitialize = q.defer();

	if (this.options.certData) {
		this.certData = this.options.certData;
	}
	else {
		fs.readFile(this.options.cert, function (err, data) {
			if (err) {
				this.deferredInitialize.reject(err);
				return;
			}
			this.certData = data.toString();
			this.checkInitialized();
		}.bind(this));
	}

	if (this.options.keyData) {
		this.keyData = this.options.keyData;
	}
	else {
		fs.readFile(this.options.key, function (err, data) {
			if (err) {
				this.deferredInitialize.reject(err);
				return;
			}
			this.keyData = data.toString();
			this.checkInitialized();
		}.bind(this));
	}

	this.checkInitialized();
	return this.deferredInitialize.promise;
};

/**
 * You should call {@link Feedback#start} instead of this method
 * @private
 */
Feedback.prototype.connect = function () {
	if(this.deferredConnection) {
		return this.deferredConnection.promise;
	}

	debug("Initialising connection");
	this.deferredConnection = q.defer();
	this.initialize().then(function() {
		var socketOptions = {};

		socketOptions.key = this.keyData;
		socketOptions.cert = this.certData;
		socketOptions.passphrase = this.options.passphrase;
		socketOptions.ca = this.options.ca;

		this.socket = tls.connect(
			this.options['port'],
			this.options['address'],
			socketOptions,
			function () {
				if (!this.socket.authorized) {
					this.deferredConnection.reject(this.socket.authorizationError);
					this.deferredConnection = null;
				}

				debug("Connection established");
				this.deferredConnection.resolve();
			}.bind(this));

		this.readBuffer = new Buffer(0);
		this.socket.on('data', this.receive.bind(this));
		this.socket.on("error", this.destroyConnection.bind(this));
		this.socket.once('close', this.resetConnection.bind(this));
	}.bind(this)).fail(function (error) {
		debug("Module initialisation error:", error);
		this.deferredConnection.reject(error);
		this.deferredConnection = null;
	}.bind(this));

	return this.deferredConnection.promise;
};

/**
 * @private
 */
Feedback.prototype.receive = function (data) {
	var time = 0;
	var tokenLength = 0;
	var token = null;

	debug("Received packet of length: %d", data.length);
	var newBuffer = new Buffer(this.readBuffer.length + data.length);
	this.readBuffer.copy(newBuffer);
	data.copy(newBuffer, this.readBuffer.length);
	this.readBuffer = newBuffer;
	while (this.readBuffer.length > 6) {
		time = this.readBuffer.readUInt32BE(0);
		tokenLength = this.readBuffer.readUInt16BE(4);
		if ((this.readBuffer.length - 6) < tokenLength) {
			return;
		}
		token = new Buffer(tokenLength);
		this.readBuffer.copy(token, 0, 6, 6 + tokenLength);

		debug("Parsed device token: %s, timestamp: %d", token.toString("hex"), time);
		if (typeof this.options.feedback == 'function') {
			debug("Calling feedback method for device");
			this.options.feedback(time, new Device(token));
		}
		this.readBuffer = this.readBuffer.slice(6 + tokenLength);
	}
}

/**
 * @private
 */
Feedback.prototype.destroyConnection = function () {
	debug("Destroying connection");
	if (this.socket) {
		this.socket.destroySoon();
	}
};

/**
 * @private
 */
Feedback.prototype.resetConnection = function () {
	debug("Resetting connection");
	if (this.socket) {
		this.socket.removeAllListeners();
	}

	if(!this.deferredConnection.promise.isResolved()) {
		debug("Connection error occurred before TLS Handshake");
		this.deferredConnection.reject(new Error("Unable to connect"));
	}

	this.socket = null;
	this.deferredConnection = null;
};

/**
 * Connect to the feedback service, also initialise the timer if an interval is specified.
 */
Feedback.prototype.start = function () {
	debug("Starting feedback service");
	this.cancel();
	if (this.options.interval > 0) {
		debug("Feedback service interval set at: %d", this.options.interval);
		this.interval = setInterval(this.request.bind(this), this.options.interval * 1000);
	}
	this.request();
};

/**
 * @private
 */
Feedback.prototype.request = function () {
	debug("Performing feedback request");
	this.connect().fail(function (error) {
		if(typeof this.options.errorCallback == "function") {
			this.options.errorCallback(error);
		}
	}.bind(this));
};

/**
 * Cancel the timer to stop the Feedback service periodically connecting.
 */
Feedback.prototype.cancel = function () {
	debug("Cancelling feedback interval");
	if (this.interval !== undefined) {
		clearInterval(this.interval);
	}
};

module.exports = Feedback;<commit_msg>Add support for rejectUnauthorized in socket options when using a local proxy<commit_after>var Device = require('./device');
var Errors = require('./errors');

var fs   = require('fs');
var q    = require('q');
var tls  = require('tls');
var util = require('./util');
var debug = function() {};
if(process.env.DEBUG) {
	try {
		debug = require('debug')('apnfb');
	}
	catch (e) {
		console.log("Notice: 'debug' module is not available. This should be installed with `npm install debug` to enable debug messages", e);
		debug = function() {};
	}
}
/**
 * Create a new connection to the APN Feedback.
 * @constructor
 * @param {Object} [options]
 * @config {String} [cert="cert.pem"] The filename of the connection certificate to load from disk
 * @config {Buffer|String} [certData] The certificate data. If supplied, will be used instead of loading from disk.
 * @config {String} [key="key.pem"] The filename of the connection key to load from disk
 * @config {Buffer|String} [keyData] The key data. If supplied will be used instead of loading from disk.
 * @config {String} [passphrase] The passphrase for the connection key, if required
 * @config {Buffer[]|String[]} [ca] An array of strings or Buffers of trusted certificates. If this is omitted several well known "root" CAs will be used, like VeriSign. - You may need to use this as some environments don't include the CA used by Apple
 * @config {String} [address="feedback.push.apple.com"] The feedback server to connect to.
 * @config {Number} [port=2195] Feedback server port
 * @config {Function} [feedback] A callback which accepts 2 parameters (timestamp, {@link Device}). See: {@link Communicating with APS.
 * @config {Function} [errorCallback] Callback which will capture connection errors
 * @config {Number} [interval=3600] Interval to automatically connect to the Feedback service.
 */
function Feedback(options) {

	this.options = {
		cert: 'cert.pem',					/* Certificate file */
		certData: null,						/* Certificate data */
		key: 'key.pem',						/* Key file */
		keyData: null,						/* Key data */
		passphrase: null,                   /* Passphrase for key */
		ca: null,							/* Certificate Authority
		address: 'feedback.push.apple.com',	/* feedback address */
		port: 2196,							/* feedback port */
		rejectUnauthorized: true,  /* Set this to false incase using a local proxy, reject otherwise */
		feedback: false,					/* enable feedback service, set to callback */
		errorCallback: false,				/* error handler to catch connection exceptions */
		interval: 3600,						/* interval in seconds to connect to feedback service */
	};

	util.extend(this.options, options);

	this.certData = null;
	this.keyData = null;

	this.deferredInitialize = null;
	this.deferredConnection = null;

	this.readBuffer = null;
	this.interval = null;

	if (typeof this.options.feedback != 'function') {
		throw new Error(-1, 'A callback function must be specified');
	}

	this.start();
};

/**
 * @private
 */
Feedback.prototype.checkInitialized = function () {
	if (this.keyData && this.certData) {
		this.deferredInitialize.resolve();
	}
};

/**
 * @private
 */
Feedback.prototype.initialize = function () {
	if (this.deferredInitialize) {
		return this.deferredInitialize.promise;
	}

	debug("Initialising module");
	this.deferredInitialize = q.defer();

	if (this.options.certData) {
		this.certData = this.options.certData;
	}
	else {
		fs.readFile(this.options.cert, function (err, data) {
			if (err) {
				this.deferredInitialize.reject(err);
				return;
			}
			this.certData = data.toString();
			this.checkInitialized();
		}.bind(this));
	}

	if (this.options.keyData) {
		this.keyData = this.options.keyData;
	}
	else {
		fs.readFile(this.options.key, function (err, data) {
			if (err) {
				this.deferredInitialize.reject(err);
				return;
			}
			this.keyData = data.toString();
			this.checkInitialized();
		}.bind(this));
	}

	this.checkInitialized();
	return this.deferredInitialize.promise;
};

/**
 * You should call {@link Feedback#start} instead of this method
 * @private
 */
Feedback.prototype.connect = function () {
	if(this.deferredConnection) {
		return this.deferredConnection.promise;
	}

	debug("Initialising connection");
	this.deferredConnection = q.defer();
	this.initialize().then(function() {
		var socketOptions = {};

		socketOptions.key = this.keyData;
		socketOptions.cert = this.certData;
		socketOptions.passphrase = this.options.passphrase;
		socketOptions.ca = this.options.ca;
		socketOptions.rejectUnauthorized = this.options.rejectUnauthorized;

		this.socket = tls.connect(
			this.options['port'],
			this.options['address'],
			socketOptions,
			function () {
				debug("Connection established");
				this.deferredConnection.resolve();
			}.bind(this));

		this.readBuffer = new Buffer(0);
		this.socket.on('data', this.receive.bind(this));
		this.socket.on("error", this.destroyConnection.bind(this));
		this.socket.once('close', this.resetConnection.bind(this));
	}.bind(this)).fail(function (error) {
		debug("Module initialisation error:", error);
		this.deferredConnection.reject(error);
		this.deferredConnection = null;
	}.bind(this));

	return this.deferredConnection.promise;
};

/**
 * @private
 */
Feedback.prototype.receive = function (data) {
	var time = 0;
	var tokenLength = 0;
	var token = null;

	debug("Received packet of length: %d", data.length);
	var newBuffer = new Buffer(this.readBuffer.length + data.length);
	this.readBuffer.copy(newBuffer);
	data.copy(newBuffer, this.readBuffer.length);
	this.readBuffer = newBuffer;
	while (this.readBuffer.length > 6) {
		time = this.readBuffer.readUInt32BE(0);
		tokenLength = this.readBuffer.readUInt16BE(4);
		if ((this.readBuffer.length - 6) < tokenLength) {
			return;
		}
		token = new Buffer(tokenLength);
		this.readBuffer.copy(token, 0, 6, 6 + tokenLength);

		debug("Parsed device token: %s, timestamp: %d", token.toString("hex"), time);
		if (typeof this.options.feedback == 'function') {
			debug("Calling feedback method for device");
			this.options.feedback(time, new Device(token));
		}
		this.readBuffer = this.readBuffer.slice(6 + tokenLength);
	}
}

/**
 * @private
 */
Feedback.prototype.destroyConnection = function () {
	debug("Destroying connection");
	if (this.socket) {
		this.socket.destroySoon();
	}
};

/**
 * @private
 */
Feedback.prototype.resetConnection = function () {
	debug("Resetting connection");
	if (this.socket) {
		this.socket.removeAllListeners();
	}

	if(!this.deferredConnection.promise.isResolved()) {
		debug("Connection error occurred before TLS Handshake");
		this.deferredConnection.reject(new Error("Unable to connect"));
	}

	this.socket = null;
	this.deferredConnection = null;
};

/**
 * Connect to the feedback service, also initialise the timer if an interval is specified.
 */
Feedback.prototype.start = function () {
	debug("Starting feedback service");
	this.cancel();
	if (this.options.interval > 0) {
		debug("Feedback service interval set at: %d", this.options.interval);
		this.interval = setInterval(this.request.bind(this), this.options.interval * 1000);
	}
	this.request();
};

/**
 * @private
 */
Feedback.prototype.request = function () {
	debug("Performing feedback request");
	this.connect().fail(function (error) {
		if(typeof this.options.errorCallback == "function") {
			this.options.errorCallback(error);
		}
	}.bind(this));
};

/**
 * Cancel the timer to stop the Feedback service periodically connecting.
 */
Feedback.prototype.cancel = function () {
	debug("Cancelling feedback interval");
	if (this.interval !== undefined) {
		clearInterval(this.interval);
	}
};

module.exports = Feedback;<|endoftext|>


<commit_before>/**
 *  FsTools
 *
 *  Collection of FS related tools, that stdlib lack of.
 **/

'use strict';

// stdlib
var fs = require('fs');
var path_join = require('path').join;
var path_exists = require('path').exists;
var path_normalize = require('path').normalize;
var dirname = require('path').dirname;

// 3rd-party
var async = require('async');
var _ = require('underscore');

// epxorts: walk, mkdir, copy, remove
var fstools = module.exports = {};

// INTERNAL HELPERS
////////////////////////////////////////////////////////////////////////////////

//  walk_flat(path, iterator, callback) -> void
//  - path (String): Path to iterate through
//  - iterator (Function): Will be fired on each element within `path`
//  - callback (Function): Will be fired once all files were processed
//
//  Walks through given `path` and calls `iterator(path, stats, callback)` for
//  each found entry (regardless to it's type) and waits for all `callback`s
//  (passed to iterator) to be fired. After all callbacks were fired, fires
//  `callback` (given to walk_flat itself).
//
//  NOTICE: It walks through single dimension of file system - it won't go into
//          found sub-directories. See `walk_recursive` for this puprpose.
//
//  Example:
//
//    walk_flat('/home/nodeca', function (path, stats, callback) {
//      if ('/home/nodeca/secrets.yml' === path) {
//        callback(Error("There is secrets file."));
//        return;
//      }
//
//      if ('/home/nodeca/xxx' === path && stats.isDirectory()) {
//        callback(Error("Path contains xxx directory."));
//        return;
//      }
//
//      callback();
//    }, function (err) {
//      if (err) console.error(err);
//      console.log('Done');
//    });
function walk_flat(path, iterator, callback) {
  async.waterfall([
    async.apply(fs.readdir, path),
    function (files, next) {
      async.forEach(files, function (file, next_file) {
        var filename = path_join(path, file);

        async.waterfall([
          async.apply(fs.lstat, filename),
          function (stats, callback) {
            iterator(filename, stats, callback);
          }
        ], next_file);
      }, next);
    }
  ], callback);
}

//  walk_recursive(path, iterator, callback) -> void
//
//  Proxies call to `walk_flat`, but wraps `iterator` so that it will
//  recursively go deeper inside each found directoy instead of calling
//  iterator on it.
function walk_recursive(path, iterator, callback) {
  // walk through given path
  walk_flat(path, function (path, stats, callback) {
    // go deeper inside
    if (stats.isDirectory()) {
      walk_recursive(path, iterator, callback);
      return;
    }

    // call iterator if not directory
    iterator(path, stats, callback);
  }, callback);
}

function copy_file(src, dst, callback) {
  var ifd, ofd;

  // create streams
  ifd = fs.createReadStream(src, {bufferSize: 64 * 1024}).on('error', callback);
  ofd = fs.createWriteStream(dst).on('error', callback).on('close', callback);

  // pipe src to dst
  ifd.pipe(ofd);
}

// PUBLIC API
////////////////////////////////////////////////////////////////////////////////

/**
 *  FsTools.walk(path, pattern, iterator[, callback]) -> void
 *  FsTools.walk(path, iterator[, callback]) -> void
 *
 *  Walks throught all files withing `path` (including sub-dirs) and calls
 *  `iterator` on each found file (or block device etc.) matching `pattern`.
 *  If no `pattern` was given - will fire call `iterator` for every single
 *  path found. After all iterations will call `callback` (if it was specified)
 *  with passing `error` as first arguemtn if there was an error.
 *
 *  ##### Iterator
 *
 *  All iterations are running within promise. So `callback` given to the `walk`
 *  will fire only after all `iterator` callbacks willnotify they finished their
 *  work:
 *
 *      var iterator = function (path, stats, callback) {
 *        // ... do something
 *        if (err) {
 *          // ... if error occured we can "stop" walker
 *          callback(err);
 *          return;
 *        }
 *        // ... if everything is good and finished notify walker we're done
 *        callback();
 *      };
 *
 *  Iterator is called with following arguments:
 *
 *  - `path` (String): Full path of the found element (e.g. `/foo/bar.txt`)
 *  - `stats` (fs.Stats): Stats object of found path
 *  - `callback` (Function): Callback function to call after path processing
 *
 *
 *  ##### Example
 *
 *      fstools.walk('/home/nodeca', function (path, stats, callback) {
 *        if (stats.isBlockDevice()) {
 *          callback(Error("WTF? Block devices are not expetcted in my room"));
 *          return;
 *        }
 *
 *        if (stats.isSocket()) {
 *          console.log("Finally I found my socket");
 *        }
 *
 *        callback();
 *      }, function (err) {
 *        if (err) {
 *          // shit happens!
 *          console.error(err);
 *          process.exit(1);
 *        }
 *
 *        console.log("Hooray! We're done!");
 *      });
 *
 *
 *  ##### Example (using pattern matching)
 *
 *      fstools.walk('/home/nodeca', '\.yml$', function (path, stats, callback) {
 *        fs.readFile(path, 'utf-8', funtion (err, str) {
 *          if (err) {
 *            callback(err);
 *            return;
 *          }
 *
 *          console.log(str);
 *          callback();
 *        });
 *      }, function (err) {
 *        if (err) {
 *          console.error(err);
 *        }
 *
 *        console.log('Done!');
 *      });
 **/
fstools.walk = function (path, pattern, iterator, callback) {
  var match; // function that tells whenever iterator need to be called or not

  if (_.isFunction(pattern)) {
    // Scenario: FileWalker(path, iterator[, callback])
    callback = iterator;
    iterator = pattern;
    match = function () { return true; };
  } else {
    // Scenario: FileWalker(path, pattern, iterator[, callback])
    pattern = new RegExp(pattern);
    match = function (path) { return pattern.test(path); };
  }

  if (!_.isFunction(callback)) {
    // Scenario: FileWalker(path, pattern, iterator)
    //       or: FileWalker(path, iterator)
    callback = function () {};
  }

  path = path_normalize(path);
  path_exists(path, function (exists) {
    if (!exists) {
      callback();
      return;
    }

    // start walking
    walk_recursive(path_normalize(path), function (path, stats, next) {
      // call iterator
      if (match(path)) {
        iterator(path, stats, next);
        return;
      }

      // skip entry
      next();
    }, callback);
  });
};

/**
 *  FsTools.remove(path, callback) -> void
 *  - path (String): Path to remove
 *  - callback (Function): Fired after path was removed
 *
 *  Removes given `path`. If it was a directory will remove it recursively,
 *  similar to UNIX' `rm -rf <path>`. After all will fire `callback(err)` with
 *  an error if there were any.
 *
 *  If given `path` was file - will proxy call to `fs.unlink`.
 *
 *
 *  ##### Example
 *
 *      fstools.remove('/home/nodeca/trash', function (err) {
 *        if (err) {
 *          console.log("U can't touch that");
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("It's Hammer time");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.remove = function (path, callback) {
  path = path_normalize(path);
  fs.lstat(path, function (err, stats) {
    if (err) {
      // file/dir not exists - no need to do anything
      if ('ENOENT' === err.code) {
        callback(null);
        return;
      }

      // unknown error - can't continue
      callback(err);
      return;
    }

    if (!stats.isDirectory()) {
      fs.unlink(path, callback);
      return;
    }

    async.series([
      async.apply(walk_flat, path, function (sub_path, stats, next) {
        fstools.remove(sub_path, next);
      }),
      async.apply(fs.rmdir, path)
    ], function (err, results) {
      callback(err);
    });
  });
};

/**
 *  FsTools.mkdir(path, mode, callback) -> void
 *  FsTools.mkdir(path, callback) -> void
 *  - path (String): Path to create
 *  - mode (String|Number): Permission mode of new directory. See stdlib
 *    fs.mkdir for details. Default: '0755'.
 *  - callback (Function): Fired after path was created
 *
 *  Creates given path, creating parents recursively if needed.
 *  Similar to UNIX' `mkdir -pf <path>`. After all will fire `callback(err)` with
 *  an error if there were any.
 *
 *
 *  ##### Example
 *
 *      fstools.mkdir('/home/nodeca/media/xxx', function (err) {
 *        if (err) {
 *          console.log("Can't' create directory");
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("We can now store some romantic movies here");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.mkdir = function (path, mode, callback) {
  if (undefined === callback && _.isFunction(mode)) {
    callback = mode;
    mode = '0755';
  }

  path = path_normalize(path);
  path_exists(path, function (exists) {
    var parent;

    if (exists) {
      callback(null);
      return;
    }

    parent = dirname(path);
    fstools.mkdir(parent, mode, function (err) {
      if (err) {
        callback(err);
        return;
      }

      fs.mkdir(path, mode, function (err) {
        // EEXIST is not error in our case
        // but a race condition :((
        if (err && 'EEXIST' === err.code) {
          callback(null);
          return;
        }

        // fallback to default behavior
        callback(err);
      });
    });
  });
};

/**
 *  FsTools.copy(src, dst, callback) -> void
 *  - src (String): Source file
 *  - dst (String): Destination file
 *  - callback (Function): Fired after path has been copied
 *
 *  Copies `src` to `dst`, creates directory for given `dst` with
 *  [[FsTools.mkdir]] if needed. Fires `callback(err)` upon
 *  completion.
 *
 *  ##### Example
 *
 *      var src = '/home/nodeca/secrets.yml',
 *          dst = '/home/nodeca/very/deep/secrets/main.yml';
 *
 *      fstools.copy(src, dst, function (err) {
 *        if (err) {
 *          console.log("Failed copy " + src + " into " + dst);
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("Done!");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.copy = function (src, dst, callback) {
  src = path_normalize(src);
  dst = path_normalize(dst);

  // sad but true - people make mistakes...
  if (src === dst) {
    callback(null);
    return;
  }

  fs.lstat(src, function (err, stats) {
    if (err) {
      callback(err);
      return;
    }

    fstools.mkdir(dirname(dst), function (err) {
      var chmod, done;

      if (err) {
        callback(err);
        return;
      }

      // chmod dst
      chmod = async.apply(fs.chmod, dst, stats.mode.toString(8).slice(-4));

      // reject async.series' results
      done = function (err/*, results */) { callback(err); };

      // *** file
      if (stats.isFile()) {
        async.series([async.apply(copy_file, src, dst), chmod], done);
        return;
      }

      // *** symbolic link
      if (stats.isSymbolicLink()) {
        async.waterfall([
          async.apply(fs.readlink, src),
          function (linkpath, next) {
            fs.symlink(linkpath, dst, next);
          },
          chmod
        ], done);
        return;
      }

      // *** directory
      if (stats.isDirectory()) {
        async.series([
          async.apply(fs.mkdir, dst, '0755'),
          function (next) {
            walk_flat(src, function (sub_src, sub_stats, sub_next) {
              fstools.copy(sub_src, dst + sub_src.replace(src, ''), sub_next);
            }, next);
          },
          chmod
        ], done);
        return;
      }

      // *** unsupported src
      callback(new Error("Unsupported type of the source"));
    });
  });
};

////////////////////////////////////////////////////////////////////////////////
// vim:ts=2:sw=2
////////////////////////////////////////////////////////////////////////////////
<commit_msg>simplify walk_flat<commit_after>/**
 *  FsTools
 *
 *  Collection of FS related tools, that stdlib lack of.
 **/

'use strict';

// stdlib
var fs = require('fs');
var path_join = require('path').join;
var path_exists = require('path').exists;
var path_normalize = require('path').normalize;
var dirname = require('path').dirname;

// 3rd-party
var async = require('async');
var _ = require('underscore');

// epxorts: walk, mkdir, copy, remove
var fstools = module.exports = {};

// INTERNAL HELPERS
////////////////////////////////////////////////////////////////////////////////

//  walk_flat(path, iterator, callback) -> void
//  - path (String): Path to iterate through
//  - iterator (Function): Will be fired on each element within `path`
//  - callback (Function): Will be fired once all files were processed
//
//  Walks through given `path` and calls `iterator(path, stats, callback)` for
//  each found entry (regardless to it's type) and waits for all `callback`s
//  (passed to iterator) to be fired. After all callbacks were fired, fires
//  `callback` (given to walk_flat itself).
//
//  NOTICE: It walks through single dimension of file system - it won't go into
//          found sub-directories. See `walk_recursive` for this puprpose.
//
//  Example:
//
//    walk_flat('/home/nodeca', function (path, stats, callback) {
//      if ('/home/nodeca/secrets.yml' === path) {
//        callback(Error("There is secrets file."));
//        return;
//      }
//
//      if ('/home/nodeca/xxx' === path && stats.isDirectory()) {
//        callback(Error("Path contains xxx directory."));
//        return;
//      }
//
//      callback();
//    }, function (err) {
//      if (err) console.error(err);
//      console.log('Done');
//    });
function walk_flat(path, iterator, callback) {
  fs.readdir(path, function (err, files) {
    if (err) {
      callback(err);
      return;
    }

    async.forEach(files, function (file, next) {
      iterator(path_join(path, file), next);
    }, callback);
  });
}

//  walk_recursive(path, iterator, callback) -> void
//
//  Proxies call to `walk_flat`, but wraps `iterator` so that it will
//  recursively go deeper inside each found directoy instead of calling
//  iterator on it.
function walk_recursive(path, iterator, callback) {
  // walk through given path
  walk_flat(path, function (path, callback) {
    fs.lstat(path, function (err, stats) {
      if (err) {
        callback(err);
        return;
      }

      // go deeper inside
      if (stats.isDirectory()) {
        walk_recursive(path, iterator, callback);
        return;
      }

      // call iterator if not directory
      iterator(path, stats, callback);
    });
  }, callback);
}

function copy_file(src, dst, callback) {
  var ifd, ofd;

  // create streams
  ifd = fs.createReadStream(src, {bufferSize: 64 * 1024}).on('error', callback);
  ofd = fs.createWriteStream(dst).on('error', callback).on('close', callback);

  // pipe src to dst
  ifd.pipe(ofd);
}

// PUBLIC API
////////////////////////////////////////////////////////////////////////////////

/**
 *  FsTools.walk(path, pattern, iterator[, callback]) -> void
 *  FsTools.walk(path, iterator[, callback]) -> void
 *
 *  Walks throught all files withing `path` (including sub-dirs) and calls
 *  `iterator` on each found file (or block device etc.) matching `pattern`.
 *  If no `pattern` was given - will fire call `iterator` for every single
 *  path found. After all iterations will call `callback` (if it was specified)
 *  with passing `error` as first arguemtn if there was an error.
 *
 *  ##### Iterator
 *
 *  All iterations are running within promise. So `callback` given to the `walk`
 *  will fire only after all `iterator` callbacks willnotify they finished their
 *  work:
 *
 *      var iterator = function (path, stats, callback) {
 *        // ... do something
 *        if (err) {
 *          // ... if error occured we can "stop" walker
 *          callback(err);
 *          return;
 *        }
 *        // ... if everything is good and finished notify walker we're done
 *        callback();
 *      };
 *
 *  Iterator is called with following arguments:
 *
 *  - `path` (String): Full path of the found element (e.g. `/foo/bar.txt`)
 *  - `stats` (fs.Stats): Stats object of found path
 *  - `callback` (Function): Callback function to call after path processing
 *
 *
 *  ##### Example
 *
 *      fstools.walk('/home/nodeca', function (path, stats, callback) {
 *        if (stats.isBlockDevice()) {
 *          callback(Error("WTF? Block devices are not expetcted in my room"));
 *          return;
 *        }
 *
 *        if (stats.isSocket()) {
 *          console.log("Finally I found my socket");
 *        }
 *
 *        callback();
 *      }, function (err) {
 *        if (err) {
 *          // shit happens!
 *          console.error(err);
 *          process.exit(1);
 *        }
 *
 *        console.log("Hooray! We're done!");
 *      });
 *
 *
 *  ##### Example (using pattern matching)
 *
 *      fstools.walk('/home/nodeca', '\.yml$', function (path, stats, callback) {
 *        fs.readFile(path, 'utf-8', funtion (err, str) {
 *          if (err) {
 *            callback(err);
 *            return;
 *          }
 *
 *          console.log(str);
 *          callback();
 *        });
 *      }, function (err) {
 *        if (err) {
 *          console.error(err);
 *        }
 *
 *        console.log('Done!');
 *      });
 **/
fstools.walk = function (path, pattern, iterator, callback) {
  var match; // function that tells whenever iterator need to be called or not

  if (_.isFunction(pattern)) {
    // Scenario: FileWalker(path, iterator[, callback])
    callback = iterator;
    iterator = pattern;
    match = function () { return true; };
  } else {
    // Scenario: FileWalker(path, pattern, iterator[, callback])
    pattern = new RegExp(pattern);
    match = function (path) { return pattern.test(path); };
  }

  if (!_.isFunction(callback)) {
    // Scenario: FileWalker(path, pattern, iterator)
    //       or: FileWalker(path, iterator)
    callback = function () {};
  }

  path = path_normalize(path);
  path_exists(path, function (exists) {
    if (!exists) {
      callback();
      return;
    }

    // start walking
    walk_recursive(path_normalize(path), function (path, stats, next) {
      // call iterator
      if (match(path)) {
        iterator(path, stats, next);
        return;
      }

      // skip entry
      next();
    }, callback);
  });
};

/**
 *  FsTools.remove(path, callback) -> void
 *  - path (String): Path to remove
 *  - callback (Function): Fired after path was removed
 *
 *  Removes given `path`. If it was a directory will remove it recursively,
 *  similar to UNIX' `rm -rf <path>`. After all will fire `callback(err)` with
 *  an error if there were any.
 *
 *  If given `path` was file - will proxy call to `fs.unlink`.
 *
 *
 *  ##### Example
 *
 *      fstools.remove('/home/nodeca/trash', function (err) {
 *        if (err) {
 *          console.log("U can't touch that");
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("It's Hammer time");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.remove = function (path, callback) {
  path = path_normalize(path);
  fs.lstat(path, function (err, stats) {
    if (err) {
      // file/dir not exists - no need to do anything
      if ('ENOENT' === err.code) {
        callback(null);
        return;
      }

      // unknown error - can't continue
      callback(err);
      return;
    }

    if (!stats.isDirectory()) {
      fs.unlink(path, callback);
      return;
    }

    async.series([
      async.apply(walk_flat, path, fstools.remove),
      async.apply(fs.rmdir, path)
    ], function (err, results) {
      callback(err);
    });
  });
};

/**
 *  FsTools.mkdir(path, mode, callback) -> void
 *  FsTools.mkdir(path, callback) -> void
 *  - path (String): Path to create
 *  - mode (String|Number): Permission mode of new directory. See stdlib
 *    fs.mkdir for details. Default: '0755'.
 *  - callback (Function): Fired after path was created
 *
 *  Creates given path, creating parents recursively if needed.
 *  Similar to UNIX' `mkdir -pf <path>`. After all will fire `callback(err)` with
 *  an error if there were any.
 *
 *
 *  ##### Example
 *
 *      fstools.mkdir('/home/nodeca/media/xxx', function (err) {
 *        if (err) {
 *          console.log("Can't' create directory");
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("We can now store some romantic movies here");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.mkdir = function (path, mode, callback) {
  if (undefined === callback && _.isFunction(mode)) {
    callback = mode;
    mode = '0755';
  }

  path = path_normalize(path);
  path_exists(path, function (exists) {
    var parent;

    if (exists) {
      callback(null);
      return;
    }

    parent = dirname(path);
    fstools.mkdir(parent, mode, function (err) {
      if (err) {
        callback(err);
        return;
      }

      fs.mkdir(path, mode, function (err) {
        // EEXIST is not error in our case
        // but a race condition :((
        if (err && 'EEXIST' === err.code) {
          callback(null);
          return;
        }

        // fallback to default behavior
        callback(err);
      });
    });
  });
};

/**
 *  FsTools.copy(src, dst, callback) -> void
 *  - src (String): Source file
 *  - dst (String): Destination file
 *  - callback (Function): Fired after path has been copied
 *
 *  Copies `src` to `dst`, creates directory for given `dst` with
 *  [[FsTools.mkdir]] if needed. Fires `callback(err)` upon
 *  completion.
 *
 *  ##### Example
 *
 *      var src = '/home/nodeca/secrets.yml',
 *          dst = '/home/nodeca/very/deep/secrets/main.yml';
 *
 *      fstools.copy(src, dst, function (err) {
 *        if (err) {
 *          console.log("Failed copy " + src + " into " + dst);
 *          console.err(err);
 *          process.exit(1);
 *        } else {
 *          console.log("Done!");
 *          process.exit(0);
 *        }
 *      });
 **/
fstools.copy = function (src, dst, callback) {
  src = path_normalize(src);
  dst = path_normalize(dst);

  // sad but true - people make mistakes...
  if (src === dst) {
    callback(null);
    return;
  }

  fs.lstat(src, function (err, stats) {
    if (err) {
      callback(err);
      return;
    }

    fstools.mkdir(dirname(dst), function (err) {
      var chmod, done;

      if (err) {
        callback(err);
        return;
      }

      // chmod dst
      chmod = async.apply(fs.chmod, dst, stats.mode.toString(8).slice(-4));

      // reject async.series' results
      done = function (err/*, results */) { callback(err); };

      // *** file
      if (stats.isFile()) {
        async.series([async.apply(copy_file, src, dst), chmod], done);
        return;
      }

      // *** symbolic link
      if (stats.isSymbolicLink()) {
        async.waterfall([
          async.apply(fs.readlink, src),
          function (linkpath, next) {
            fs.symlink(linkpath, dst, next);
          },
          chmod
        ], done);
        return;
      }

      // *** directory
      if (stats.isDirectory()) {
        async.series([
          async.apply(fs.mkdir, dst, '0755'),
          async.apply(walk_flat, src, function (path, next) {
            fstools.copy(path, dst + path.replace(src, ''), next);
          }),
          chmod
        ], done);
        return;
      }

      // *** unsupported src
      callback(new Error("Unsupported type of the source"));
    });
  });
};

////////////////////////////////////////////////////////////////////////////////
// vim:ts=2:sw=2
////////////////////////////////////////////////////////////////////////////////
<|endoftext|>