<commit_before>"use strict";

var utils = require("../utils");
var log = require("npmlog");
var bluebird = require("bluebird");

module.exports = function(defaultFuncs, api, ctx) {
  function uploadAttachment(attachments, callback) {
    var uploads = [];

    // create an array of promises
    for (var i = 0; i < attachments.length; i++) {
      if (!utils.isReadableStream(attachments[i])) {
        throw {error: "Attachment should be a readable stream and not " + utils.getType(attachments[i]) + "."};
      }

      var form = {
        upload_1024: attachments[i],
      };

      uploads.push(defaultFuncs
        .postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, form, {})
        .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs))
        .then(function (resData) {
          if (resData.error) {
            throw resData;
          }

          // We have to return the data unformatted unless we want to change it
          // back in sendMessage.
          return resData.payload.metadata[0];
        }));
    }

    // resolve all promises
    bluebird
      .all(uploads)
      .then(function(resData) {
        callback(null, resData);
      })
      .catch(function(err) {
        log.error("Error in uploadAttachment", err);
        return callback(err);
      });
  }

  return function sendMessage(msg, threadID, callback) {
    if(!callback && utils.getType(threadID) === 'Function') {
      throw {error: "please pass a threadID as a second argument."};
    }
    if(!callback) {
      callback = function() {};
    }

    var msgType = utils.getType(msg);
    var threadIDType = utils.getType(threadID);

    if(msgType !== "String" && msgType !== "Object") {
      throw {error: "Message should be of type string or object and not " + threadIDType + "."};
    }

    // Changing this to accomodate an array of users
    if(threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") {
      throw {error: "ThreadID should be of type number, string, or array and not " + threadIDType + "."};
    }

    if (msgType === "String") {
      msg = { body: msg };
    }

    var messageAndOTID = utils.generateOfflineThreadingID();

    var form = {
      'client' : 'mercury',
      'message_batch[0][action_type]' : 'ma-type:user-generated-message',
      'message_batch[0][author]' : 'fbid:' + ctx.userID,
      'message_batch[0][timestamp]' : Date.now(),
      'message_batch[0][timestamp_absolute]' : 'Today',
      'message_batch[0][timestamp_relative]' : utils.genTimestampRelative(),
      'message_batch[0][timestamp_time_passed]' : '0',
      'message_batch[0][is_unread]' : false,
      'message_batch[0][is_cleared]' : false,
      'message_batch[0][is_forward]' : false,
      'message_batch[0][is_filtered_content]' : false,
      'message_batch[0][is_filtered_content_bh]':false,
      'message_batch[0][is_filtered_content_account]':false,
      'message_batch[0][is_filtered_content_quasar]':false,
      'message_batch[0][is_filtered_content_invalid_app]':false,
      'message_batch[0][is_spoof_warning]' : false,
      'message_batch[0][source]' : 'source:chat:web',
      'message_batch[0][source_tags][0]' : 'source:chat',
      'message_batch[0][body]' : msg.body ? msg.body.toString() : "",
      'message_batch[0][html_body]' : false,
      'message_batch[0][ui_push_phase]' : 'V3',
      'message_batch[0][status]' : '0',
      'message_batch[0][offline_threading_id]' : messageAndOTID,
      'message_batch[0][message_id]' : messageAndOTID,
      'message_batch[0][threading_id]': utils.generateThreadingID(ctx.clientID),
      'message_batch[0][ephemeral_ttl_mode]:': '0',
      'message_batch[0][manual_retry_cnt]' : '0',
      'message_batch[0][has_attachment]' : false,
      'message_batch[0][signatureID]' : utils.getSignatureID(),
    };

    if (msg.attachment) {
      form['message_batch[0][has_attachment]'] = true;
      form['message_batch[0][image_ids]'] = [];
      form['message_batch[0][gif_ids]'] = [];
      form['message_batch[0][file_ids]'] = [];
      form['message_batch[0][video_ids]'] = [];

      if (utils.getType(msg.attachment) !== 'Array') {
        msg.attachment = [msg.attachment];
      }

      uploadAttachment(msg.attachment, function (err, files) {
        if (err) {
          return callback(err);
        }

        files.forEach(function (file) {
          var key = Object.keys(file);
          var type = key[0]; // image_id, file_id, etc
          form['message_batch[0][' + type + 's]'].push(file[type]); // push the id
        });

        send();
      });
    } else if (msg.url) {
      form['message_batch[0][has_attachment]'] = true;
      form['message_batch[0][shareable_attachment][share_type]'] = 100;
      api.getUrl(msg.url, function (err, params) {
        if (err) {
          return callback(err);
        }

        form['message_batch[0][shareable_attachment][share_params]'] = params;
        send();
      });
    } else if (msg.sticker) {
      form['message_batch[0][has_attachment]'] = true;
      form['message_batch[0][sticker_id]'] = msg.sticker;

      // Sticker can't be combined with body
      delete msg.body;

      send();
    } else {
      send();
    }

    function sendContent(isSingleUser) {
      // There are three cases here:
      // 1. threadID is of type array, where we're starting a new group chat with users
      //    specified in the array.
      // 2. User is sending a message to a specific user.
      // 3. No additional form params and the message goes to an existing group chat.
      if(threadIDType === "Array") {
        for (var i  = 0; i < threadID.length; i++) {
          form['message_batch[0][specific_to_list][' + i + ']'] = "fbid:" + threadID[i];
        }
        form['message_batch[0][specific_to_list][' + (threadID.length) + ']'] = "fbid:" + ctx.userID;
        form['message_batch[0][client_thread_id]'] = "root:" + messageAndOTID;
        log.info("Sending message to multiple users: " + threadID);
      } else {
        // This means that threadID is the id of a user, and the chat
        // is a single person chat
        if(isSingleUser) {
          form['message_batch[0][specific_to_list][0]'] = "fbid:" + threadID;
          form['message_batch[0][specific_to_list][1]'] = "fbid:" + ctx.userID;
          form['message_batch[0][other_user_fbid]'] = threadID;
        } else {
          form['message_batch[0][thread_fbid]'] = threadID;
        }
      }

      if(ctx.globalOptions.pageID) {
        form['message_batch[0][author]'] = "fbid:" + ctx.globalOptions.pageID;
        form['message_batch[0][specific_to_list][1]'] = "fbid:" + ctx.globalOptions.pageID;
        form['message_batch[0][creator_info][creatorID]'] = ctx.userID;
        form['message_batch[0][creator_info][creatorType]'] = "direct_admin";
        form['message_batch[0][creator_info][labelType]'] = "sent_message";
        form['message_batch[0][creator_info][pageID]'] = ctx.globalOptions.pageID;
        form['request_user_id'] = ctx.globalOptions.pageID;
        form['message_batch[0][creator_info][profileURI]'] = "https://www.facebook.com/profile.php?id=" + ctx.userID;
      }

      defaultFuncs
        .post("https://www.facebook.com/ajax/mercury/send_messages.php", ctx.jar, form)
        .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs))
        .then(function(resData) {
          if (!resData) {
            throw {error: "Send message failed."};
          }

          if (resData.error) {
            throw resData;
          }

          var messageInfo = resData.payload.actions.reduce(function(p, v) {
            return {
              threadID: v.thread_fbid,
              messageID: v.message_id,
              timestamp: v.timestamp
            } || p; }, null);

          return callback(null, messageInfo);
        })
        .catch(function(err) {
          log.error("ERROR in sendMessage --> ", err);
          return callback(err);
        });
    }

    function send() {
      // We're doing a query to this to check if the given id is the id of
      // a user or of a group chat. The form will be different depending
      // on that.
      if(threadIDType === "Array") {
        sendContent(false);
      } else {
        api.getUserInfo(threadID, function(err, res) {
          if(err) {
            return callback(err);
          }
          sendContent(Object.keys(res).length > 0);
        });
      }
    }
  };
};
<commit_msg>Add warning when not part of conversation.<commit_after>"use strict";

var utils = require("../utils");
var log = require("npmlog");
var bluebird = require("bluebird");

module.exports = function(defaultFuncs, api, ctx) {
  function uploadAttachment(attachments, callback) {
    var uploads = [];

    // create an array of promises
    for (var i = 0; i < attachments.length; i++) {
      if (!utils.isReadableStream(attachments[i])) {
        throw {error: "Attachment should be a readable stream and not " + utils.getType(attachments[i]) + "."};
      }

      var form = {
        upload_1024: attachments[i],
      };

      uploads.push(defaultFuncs
        .postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, form, {})
        .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs))
        .then(function (resData) {
          if (resData.error) {
            throw resData;
          }

          // We have to return the data unformatted unless we want to change it
          // back in sendMessage.
          return resData.payload.metadata[0];
        }));
    }

    // resolve all promises
    bluebird
      .all(uploads)
      .then(function(resData) {
        callback(null, resData);
      })
      .catch(function(err) {
        log.error("Error in uploadAttachment", err);
        return callback(err);
      });
  }

  return function sendMessage(msg, threadID, callback) {
    if(!callback && utils.getType(threadID) === 'Function') {
      throw {error: "please pass a threadID as a second argument."};
    }
    if(!callback) {
      callback = function() {};
    }

    var msgType = utils.getType(msg);
    var threadIDType = utils.getType(threadID);

    if(msgType !== "String" && msgType !== "Object") {
      throw {error: "Message should be of type string or object and not " + threadIDType + "."};
    }

    // Changing this to accomodate an array of users
    if(threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") {
      throw {error: "ThreadID should be of type number, string, or array and not " + threadIDType + "."};
    }

    if (msgType === "String") {
      msg = { body: msg };
    }

    var messageAndOTID = utils.generateOfflineThreadingID();

    var form = {
      'client' : 'mercury',
      'message_batch[0][action_type]' : 'ma-type:user-generated-message',
      'message_batch[0][author]' : 'fbid:' + ctx.userID,
      'message_batch[0][timestamp]' : Date.now(),
      'message_batch[0][timestamp_absolute]' : 'Today',
      'message_batch[0][timestamp_relative]' : utils.genTimestampRelative(),
      'message_batch[0][timestamp_time_passed]' : '0',
      'message_batch[0][is_unread]' : false,
      'message_batch[0][is_cleared]' : false,
      'message_batch[0][is_forward]' : false,
      'message_batch[0][is_filtered_content]' : false,
      'message_batch[0][is_filtered_content_bh]':false,
      'message_batch[0][is_filtered_content_account]':false,
      'message_batch[0][is_filtered_content_quasar]':false,
      'message_batch[0][is_filtered_content_invalid_app]':false,
      'message_batch[0][is_spoof_warning]' : false,
      'message_batch[0][source]' : 'source:chat:web',
      'message_batch[0][source_tags][0]' : 'source:chat',
      'message_batch[0][body]' : msg.body ? msg.body.toString() : "",
      'message_batch[0][html_body]' : false,
      'message_batch[0][ui_push_phase]' : 'V3',
      'message_batch[0][status]' : '0',
      'message_batch[0][offline_threading_id]' : messageAndOTID,
      'message_batch[0][message_id]' : messageAndOTID,
      'message_batch[0][threading_id]': utils.generateThreadingID(ctx.clientID),
      'message_batch[0][ephemeral_ttl_mode]:': '0',
      'message_batch[0][manual_retry_cnt]' : '0',
      'message_batch[0][has_attachment]' : false,
      'message_batch[0][signatureID]' : utils.getSignatureID(),
    };

    if (msg.attachment) {
      form['message_batch[0][has_attachment]'] = true;
      form['message_batch[0][image_ids]'] = [];
      form['message_batch[0][gif_ids]'] = [];
      form['message_batch[0][file_ids]'] = [];
      form['message_batch[0][video_ids]'] = [];

      if (utils.getType(msg.attachment) !== 'Array') {
        msg.attachment = [msg.attachment];
      }

      uploadAttachment(msg.attachment, function (err, files) {
        if (err) {
          return callback(err);
        }

        files.forEach(function (file) {
          var key = Object.keys(file);
          var type = key[0]; // image_id, file_id, etc
          form['message_batch[0][' + type + 's]'].push(file[type]); // push the id
        });

        send();
      });
    } else if (msg.url) {
      form['message_batch[0][has_attachment]'] = true;
      form['message_batch[0][shareable_attachment][share_type]'] = 100;
      api.getUrl(msg.url, function (err, params) {
        if (err) {
          return callback(err);
        }

        form['message_batch[0][shareable_attachment][share_params]'] = params;
        send();
      });
    } else if (msg.sticker) {
      form['message_batch[0][has_attachment]'] = true;
      form['message_batch[0][sticker_id]'] = msg.sticker;

      // Sticker can't be combined with body
      delete msg.body;

      send();
    } else {
      send();
    }

    function sendContent(isSingleUser) {
      // There are three cases here:
      // 1. threadID is of type array, where we're starting a new group chat with users
      //    specified in the array.
      // 2. User is sending a message to a specific user.
      // 3. No additional form params and the message goes to an existing group chat.
      if(threadIDType === "Array") {
        for (var i  = 0; i < threadID.length; i++) {
          form['message_batch[0][specific_to_list][' + i + ']'] = "fbid:" + threadID[i];
        }
        form['message_batch[0][specific_to_list][' + (threadID.length) + ']'] = "fbid:" + ctx.userID;
        form['message_batch[0][client_thread_id]'] = "root:" + messageAndOTID;
        log.info("Sending message to multiple users: " + threadID);
      } else {
        // This means that threadID is the id of a user, and the chat
        // is a single person chat
        if(isSingleUser) {
          form['message_batch[0][specific_to_list][0]'] = "fbid:" + threadID;
          form['message_batch[0][specific_to_list][1]'] = "fbid:" + ctx.userID;
          form['message_batch[0][other_user_fbid]'] = threadID;
        } else {
          form['message_batch[0][thread_fbid]'] = threadID;
        }
      }

      if(ctx.globalOptions.pageID) {
        form['message_batch[0][author]'] = "fbid:" + ctx.globalOptions.pageID;
        form['message_batch[0][specific_to_list][1]'] = "fbid:" + ctx.globalOptions.pageID;
        form['message_batch[0][creator_info][creatorID]'] = ctx.userID;
        form['message_batch[0][creator_info][creatorType]'] = "direct_admin";
        form['message_batch[0][creator_info][labelType]'] = "sent_message";
        form['message_batch[0][creator_info][pageID]'] = ctx.globalOptions.pageID;
        form['request_user_id'] = ctx.globalOptions.pageID;
        form['message_batch[0][creator_info][profileURI]'] = "https://www.facebook.com/profile.php?id=" + ctx.userID;
      }

      defaultFuncs
        .post("https://www.facebook.com/ajax/mercury/send_messages.php", ctx.jar, form)
        .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs))
        .then(function(resData) {
          if (!resData) {
            throw {error: "Send message failed."};
          }

          if (resData.error) {
            if (resData.error === 1545012) {
              log.warn("Got error 1545012. This might mean that you're not part of the conversation " + threadID);
            }
            throw resData;
          }

          var messageInfo = resData.payload.actions.reduce(function(p, v) {
            return {
              threadID: v.thread_fbid,
              messageID: v.message_id,
              timestamp: v.timestamp
            } || p; }, null);

          return callback(null, messageInfo);
        })
        .catch(function(err) {
          log.error("ERROR in sendMessage --> ", err);
          return callback(err);
        });
    }

    function send() {
      // We're doing a query to this to check if the given id is the id of
      // a user or of a group chat. The form will be different depending
      // on that.
      if(threadIDType === "Array") {
        sendContent(false);
      } else {
        api.getUserInfo(threadID, function(err, res) {
          if(err) {
            return callback(err);
          }
          sendContent(Object.keys(res).length > 0);
        });
      }
    }
  };
};
<|endoftext|>


<commit_before>/**
 * parser server
 */
var DemoParser = require('tf2-demo');
var express = require('express');
var app = express();
var url = require('url');
var https = require('https');
var http = require('http');

app.set('port', (process.env.PORT || 80));
app.use(express.static(__dirname + '/public'));

app.get('/', function (request, response) {
	response.send('Hello World!');
});

function handleDataStream(stream, cb) {
	var buffers = [];
	stream.on('data', function (buffer) {
		buffers.push(buffer);
	});
	stream.on('end', function () {
		try {
			var buffer = Buffer.concat(buffers);
			var demo = DemoParser.Demo.fromNodeBuffer(buffer);
			var parser = demo.getParser(true);
			var header = parser.readHeader();
			var match = parser.parseBody();
			var body = match.getState();
			body.header = header;
			cb(body);
		} catch (e) {
			cb(e);
		}
	});
}

app.post('/parse', function (req, res) {
	handleDataStream(req, function (body) {
		res.set('Content-Type', 'application/json');
		res.write(JSON.stringify(body));
		res.end();
	})
});

app.listen(9123);

const chakram = require('chakram');
const expect = chakram.expect;
const root = 'http://localhost:8000/';
const fs = require('fs');

process.env.PARSER_URL = `http://localhost:9123/parse`;
process.env.EDIT_SECRET = 'edit_key';

chakram.setRequestDefaults({baseUrl: root});

before((done) => {
	console.log('spawn server');
	const server = require('child_process').spawn('php', ['-S', '0.0.0.0:8000', 'router.php'], {
		cwd: __dirname + '/../',
		env: process.env
	});
	server.stdout.on('data', (data) => {
		console.log(`stdout: ${data}`);
	});
	server.stderr.on('data', (data) => {
		console.log(`stderr: ${data}`);
	});
	after(() => {
		console.log('clean server');
		server && server.kill();
	});
	setTimeout(done, 1000);
});

before("reset db", function () {
	chakram.post("reset");
	return chakram.wait();
});

beforeEach("create test user", function () {
	chakram.post("testuser");
	return chakram.wait();
});

afterEach("reset db", function () {
	chakram.post("reset");
	return chakram.wait();
});

function uploadDemo(file) {
	return chakram.post("upload", undefined, {
		formData: {
			name: 'foo',
			blue: 'BLU',
			red: 'RED',
			demo: fs.createReadStream(file),
			key: 'key1'
		}
	}).then((response) => {
		return parseInt(response.body.match(/\/(\d+)/)[1], 10);
	});
}

chakram.addMethod("text", function (respObj, text) {
	const body = respObj.response.body;
	this.assert(body === text,
		'expected response text ' + body + ' to equal ' + text,
		'expected response text ' + body + ' to not be equal to ' + text);
});
chakram.addMethod("containsText", function (respObj, text) {
	const body = respObj.response.body;
	this.assert(body.indexOf(text) !== -1,
		'expected response text ' + body + ' to contain ' + text,
		'expected response text ' + body + ' to not contain ' + text);
});
describe("Upload", function () {
	this.timeout(1000 * 30);
	it("fails without valid key", function () {
		const response = chakram.post("upload", undefined, {
			formData: {
				name: 'foo',
				blue: 'BLU',
				red: 'RED',
				demo: fs.createReadStream(__dirname + '/../data/product.dem'),
				key: 'dummy'
			}
		});
		expect(response).to.have.status(401);
		expect(response).to.be.text('Invalid key');
		return chakram.wait();
	});

	it("returns the demo path on success", function () {
		const response = chakram.post("upload", undefined, {
			formData: {
				name: 'foo',
				blue: 'BLU',
				red: 'RED',
				demo: fs.createReadStream(__dirname + '/../data/product.dem'),
				key: 'key1'
			}
		});
		// expect(response).to.have.status(401);
		expect(response).to.be.containsText('STV available at: ');
		return chakram.wait();
	});
});

describe("Demo listing", function () {
	this.timeout(1000 * 30);

	it("starts empty", function () {
		const response = chakram.get("demos");
		expect(response).to.have.status(200);
		expect(response).to.have.header("content-type", "application/json; charset=utf-8");
		expect(response).to.comprise.of.json([]);
		return chakram.wait();
	});

	it("contains uploaded demo", function () {
		return uploadDemo(__dirname + '/../data/product.dem').then(id => {
			return chakram.get("demos").then(response => {
				const body = response.body;
				expect(body[0].id).to.be.equal(id);
				expect(body[0].name).to.be.equal('foo');
				expect(body[0].server).to.be.equal('UGC Highlander Match');
				expect(body[0].duration).to.be.equal(778);
				expect(body[0].nick).to.be.equal('SourceTV Demo');
				expect(body[0].map).to.be.equal('koth_product_rc8');
				expect(body[0].red).to.be.equal('RED');
				expect(body[0].blue).to.be.equal('BLU');
				expect(body[0].redScore).to.be.equal(3);
				expect(body[0].blueScore).to.be.equal(0);
				expect(body[0].playerCount).to.be.equal(18);
			});
		});
	});
});

describe("Set url", function () {
	this.timeout(1000 * 30);

	it("fails with invalid key", function () {
		return uploadDemo(__dirname + '/../data/product.dem').then(id => {
			return chakram.get("demos").then(response => {
				const setUrl = chakram.post(`/demos/${id}/url`, undefined, {
					formData: {
						hash: 'asd',
						backend: 'foo',
						url: 'http://bar',
						path: 'bar',
						key: 'foo'
					}
				});
				expect(setUrl).to.be.containsText('Invalid key');
				expect(setUrl).to.have.status(500);
				return chakram.wait();
			});
		});
	});

	it("fails with invalid hash", function () {
		return uploadDemo(__dirname + '/../data/product.dem').then(id => {
			return chakram.get("demos").then(response => {
				const setUrl = chakram.post(`/demos/${id}/url`, undefined, {
					formData: {
						hash: 'asd',
						backend: 'foo',
						url: 'http://bar',
						path: 'bar',
						key: 'edit_key'
					}
				});
				expect(setUrl).to.be.containsText('Invalid demo hash');
				expect(setUrl).to.have.status(500);
				return chakram.wait();
			});
		});
	});

	it("changes url, backend and path on success", function () {
		return uploadDemo(__dirname + '/../data/product.dem').then(id => {
			return chakram.get(`demos/${id}`).then(response => {
				const hash = response.body.hash;

				const setUrl = chakram.post(`/demos/${id}/url`, undefined, {
					formData: {
						hash: hash,
						backend: 'foo',
						url: 'http://bar',
						path: 'bar',
						key: 'edit_key'
					}
				});
				expect(setUrl).to.have.status(200);
				return setUrl.then(response => {
					return chakram.get(`demos/${id}`)
				}).then(response => {
					const body = response.body;
					expect(body.id).to.be.equal(id);
					expect(body.backend).to.be.equal('foo');
					expect(body.url).to.be.equal('http://bar');
					expect(body.path).to.be.equal('bar');
				});
			});
		});
	});
});
<commit_msg>better db resetting<commit_after>/**
 * parser server
 */
var DemoParser = require('tf2-demo');
var express = require('express');
var app = express();
var url = require('url');
var https = require('https');
var http = require('http');

app.set('port', (process.env.PORT || 80));
app.use(express.static(__dirname + '/public'));

app.get('/', function (request, response) {
	response.send('Hello World!');
});

function handleDataStream(stream, cb) {
	var buffers = [];
	stream.on('data', function (buffer) {
		buffers.push(buffer);
	});
	stream.on('end', function () {
		try {
			var buffer = Buffer.concat(buffers);
			var demo = DemoParser.Demo.fromNodeBuffer(buffer);
			var parser = demo.getParser(true);
			var header = parser.readHeader();
			var match = parser.parseBody();
			var body = match.getState();
			body.header = header;
			cb(body);
		} catch (e) {
			cb(e);
		}
	});
}

app.post('/parse', function (req, res) {
	handleDataStream(req, function (body) {
		res.set('Content-Type', 'application/json');
		res.write(JSON.stringify(body));
		res.end();
	})
});

app.listen(9123);

const chakram = require('chakram');
const expect = chakram.expect;
const root = 'http://localhost:8000/';
const fs = require('fs');

process.env.PARSER_URL = `http://localhost:9123/parse`;
process.env.EDIT_SECRET = 'edit_key';

chakram.setRequestDefaults({baseUrl: root});

before((done) => {
	console.log('spawn server');
	const server = require('child_process').spawn('php', ['-S', '0.0.0.0:8000', 'router.php'], {
		cwd: __dirname + '/../',
		env: process.env
	});
	server.stdout.on('data', (data) => {
		console.log(`stdout: ${data}`);
	});
	server.stderr.on('data', (data) => {
		console.log(`stderr: ${data}`);
	});
	after(() => {
		console.log('clean server');
		server && server.kill();
	});
	setTimeout(done, 1000);
});

before("reset db", function () {
	chakram.post("reset");
	return chakram.wait();
});

beforeEach("create test user", function () {
	chakram.post("reset");
	return chakram.wait().then(function() {
		chakram.post("testuser");
		return chakram.wait();
	});
});

afterEach("reset db", function () {
	chakram.post("reset");
	return chakram.wait();
});

function uploadDemo(file) {
	return chakram.post("upload", undefined, {
		formData: {
			name: 'foo',
			blue: 'BLU',
			red: 'RED',
			demo: fs.createReadStream(file),
			key: 'key1'
		}
	}).then((response) => {
		return parseInt(response.body.match(/\/(\d+)/)[1], 10);
	});
}

chakram.addMethod("text", function (respObj, text) {
	const body = respObj.response.body;
	this.assert(body === text,
		'expected response text ' + body + ' to equal ' + text,
		'expected response text ' + body + ' to not be equal to ' + text);
});
chakram.addMethod("containsText", function (respObj, text) {
	const body = respObj.response.body;
	this.assert(body.indexOf(text) !== -1,
		'expected response text ' + body + ' to contain ' + text,
		'expected response text ' + body + ' to not contain ' + text);
});
describe("Upload", function () {
	this.timeout(1000 * 30);
	it("fails without valid key", function () {
		const response = chakram.post("upload", undefined, {
			formData: {
				name: 'foo',
				blue: 'BLU',
				red: 'RED',
				demo: fs.createReadStream(__dirname + '/../data/product.dem'),
				key: 'dummy'
			}
		});
		expect(response).to.have.status(401);
		expect(response).to.be.text('Invalid key');
		return chakram.wait();
	});

	it("returns the demo path on success", function () {
		const response = chakram.post("upload", undefined, {
			formData: {
				name: 'foo',
				blue: 'BLU',
				red: 'RED',
				demo: fs.createReadStream(__dirname + '/../data/product.dem'),
				key: 'key1'
			}
		});
		// expect(response).to.have.status(401);
		expect(response).to.be.containsText('STV available at: ');
		return chakram.wait();
	});
});

describe("Demo listing", function () {
	this.timeout(1000 * 30);

	it("starts empty", function () {
		const response = chakram.get("demos");
		expect(response).to.have.status(200);
		expect(response).to.have.header("content-type", "application/json; charset=utf-8");
		expect(response).to.comprise.of.json([]);
		return chakram.wait();
	});

	it("contains uploaded demo", function () {
		return uploadDemo(__dirname + '/../data/product.dem').then(id => {
			return chakram.get("demos").then(response => {
				const body = response.body;
				expect(body[0].id).to.be.equal(id);
				expect(body[0].name).to.be.equal('foo');
				expect(body[0].server).to.be.equal('UGC Highlander Match');
				expect(body[0].duration).to.be.equal(778);
				expect(body[0].nick).to.be.equal('SourceTV Demo');
				expect(body[0].map).to.be.equal('koth_product_rc8');
				expect(body[0].red).to.be.equal('RED');
				expect(body[0].blue).to.be.equal('BLU');
				expect(body[0].redScore).to.be.equal(3);
				expect(body[0].blueScore).to.be.equal(0);
				expect(body[0].playerCount).to.be.equal(18);
			});
		});
	});
});

describe("Set url", function () {
	this.timeout(1000 * 30);

	it("fails with invalid key", function () {
		return uploadDemo(__dirname + '/../data/product.dem').then(id => {
			return chakram.get("demos").then(response => {
				const setUrl = chakram.post(`/demos/${id}/url`, undefined, {
					formData: {
						hash: 'asd',
						backend: 'foo',
						url: 'http://bar',
						path: 'bar',
						key: 'foo'
					}
				});
				expect(setUrl).to.be.containsText('Invalid key');
				expect(setUrl).to.have.status(500);
				return chakram.wait();
			});
		});
	});

	it("fails with invalid hash", function () {
		return uploadDemo(__dirname + '/../data/product.dem').then(id => {
			return chakram.get("demos").then(response => {
				const setUrl = chakram.post(`/demos/${id}/url`, undefined, {
					formData: {
						hash: 'asd',
						backend: 'foo',
						url: 'http://bar',
						path: 'bar',
						key: 'edit_key'
					}
				});
				expect(setUrl).to.be.containsText('Invalid demo hash');
				expect(setUrl).to.have.status(500);
				return chakram.wait();
			});
		});
	});

	it("changes url, backend and path on success", function () {
		return uploadDemo(__dirname + '/../data/product.dem').then(id => {
			return chakram.get(`demos/${id}`).then(response => {
				const hash = response.body.hash;

				const setUrl = chakram.post(`/demos/${id}/url`, undefined, {
					formData: {
						hash: hash,
						backend: 'foo',
						url: 'http://bar',
						path: 'bar',
						key: 'edit_key'
					}
				});
				expect(setUrl).to.have.status(200);
				return setUrl.then(response => {
					return chakram.get(`demos/${id}`)
				}).then(response => {
					const body = response.body;
					expect(body.id).to.be.equal(id);
					expect(body.backend).to.be.equal('foo');
					expect(body.url).to.be.equal('http://bar');
					expect(body.path).to.be.equal('bar');
				});
			});
		});
	});
});
<|endoftext|>


<commit_before>module.exports = function(app, settings) {
    app.get('/status.json', function(req, res) {
        res.send({ status: 'true' });
    });

    /**
     * Inspect fields
     */
    app.get('/:mapfile_64/fields.json', function(req, res) {
    });

    /**
     * Inspect data
     */
    app.get('/:mapfile_64/data.json', function(req, res) {
    });

    /**
     * Inspect layer
     */
    app.get('/:mapfile_64/:layer_64/layer.json', function(req, res) {
    });

    /**
     * Inspect field values
     */
    app.get('/:mapfile_64/:layer_64/:feature_64/values.json', function(req, res) {
    });
}

<commit_msg>Add placeholder abilities route callback.<commit_after>module.exports = function(app, settings) {
    app.get('/status.json', function(req, res) {
        res.send({ status: 'true' });
    });

    /**
     * @TODO expose available fonts (and other 'abilities').
     */
    app.get('/abilities.json', function(req, res) {
        res.send({ fonts: [] });
    });

    /**
     * Inspect fields
     */
    app.get('/:mapfile_64/fields.json', function(req, res) {
    });

    /**
     * Inspect data
     */
    app.get('/:mapfile_64/data.json', function(req, res) {
    });

    /**
     * Inspect layer
     */
    app.get('/:mapfile_64/:layer_64/layer.json', function(req, res) {
    });

    /**
     * Inspect field values
     */
    app.get('/:mapfile_64/:layer_64/:feature_64/values.json', function(req, res) {
    });
}

<|endoftext|>


<commit_before>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2015  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import React, { PropTypes, Component } from 'react';
import AvatarEditor from 'react-avatar-editor';

import ModalComponent from '../modal-component';

export default class ChangeAvatarModal extends Component {
  static displayName = 'ChangeAvatarModal';

  static propTypes = {
    onClose: PropTypes.func,
    onSave: PropTypes.func,
    visible: PropTypes.bool,
  };

  static defaultProps = {
    visible: false,
    onClose: () => {},
    onSave: () => {}
  };

  constructor(props) {
    super(props);
    this.state = {
      image: null,
      scale: 1,
      avatar: null,
      modalVisible: false
    };
  }

  onChange = (event) => {
    console.log('event: ', event);
    let file = event.target.files[0];
    this.setState({
      image: URL.createObjectURL(file),
      avatar: file
    });
  }

  handleScale = () => {
    this.setState({scale: this.refs.scale.value})
  }

  close = () => {
    this.setState({image: null});
    this.props.onClose();
  }

  save = () => {
    this.setState({image: null});
    this.props.onSave(this.state.avatar, this.refs.avatar.getCroppingRect());
  }

  editor() {
    return this.state.image ? (

              <AvatarEditor
              ref="avatar"
              image={this.state.image}
              width={350}
              height={350}
              border={50}
              color={[255, 255, 255, 0.6]} // RGBA
              scale={this.state.scale} />

                -
                <input className="change_avatar_modal__size_box__bar" name="scale" type="range" ref="scale" onChange={this.handleScale} min="1" max="2" step="0.01" defaultValue="1" />
                +

          ) : (<input type="file" className="change_avatar_modal__input" onChange={this.onChange} />);
  }

  render () {

    if (!this.props.visible) {
      return null;
    }

    return (
      <ModalComponent
        className="change_avatar_modal"
        size="normal"
        onHide={this.close}
      >
        <ModalComponent.Head>
          <ModalComponent.Title>Change your avatar</ModalComponent.Title>
        </ModalComponent.Head>
        <ModalComponent.Body>
          {this.editor()}
        </ModalComponent.Body>
        <ModalComponent.Actions>

Save

        </ModalComponent.Actions>
      </ModalComponent>
    );
  }
}
<commit_msg>happier linter<commit_after>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2015  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import React, { PropTypes, Component } from 'react';
import AvatarEditor from 'react-avatar-editor';

import ModalComponent from '../modal-component';

export default class ChangeAvatarModal extends Component {
  static displayName = 'ChangeAvatarModal';

  static propTypes = {
    onClose: PropTypes.func,
    onSave: PropTypes.func,
    visible: PropTypes.bool
  };

  static defaultProps = {
    visible: false,
    onClose: () => {},
    onSave: () => {}
  };

  constructor(props) {
    super(props);
    this.state = {
      image: null,
      scale: 1,
      avatar: null,
      modalVisible: false
    };
  }

  onChange = (event) => {
    let file = event.target.files[0];
    this.setState({
      image: URL.createObjectURL(file),
      avatar: file
    });
  };

  handleScale = () => {
    this.setState({scale: this.refs.scale.value})
  };

  close = () => {
    this.setState({image: null});
    this.props.onClose();
  };

  save = () => {
    this.setState({image: null});
    this.props.onSave(this.state.avatar, this.refs.avatar.getCroppingRect());
  };

  editor() {
    return this.state.image ? (

              <AvatarEditor
                border={50}
                color={[255, 255, 255, 0.6]} // RGBA
                height={350}
                image={this.state.image}
                ref="avatar"
                scale={this.state.scale}
                width={350}
              />

                -
                <input className="change_avatar_modal__size_box__bar" defaultValue="1" max="2" min="1" name="scale" ref="scale" step="0.01" type="range" onChange={this.handleScale} />
                +

          ) : (<input className="change_avatar_modal__input" type="file" onChange={this.onChange} />);
  }

  render () {

    if (!this.props.visible) {
      return null;
    }

    return (
      <ModalComponent
        className="change_avatar_modal"
        size="normal"
        onHide={this.close}
      >
        <ModalComponent.Head>
          <ModalComponent.Title>Change your avatar</ModalComponent.Title>
        </ModalComponent.Head>
        <ModalComponent.Body>
          {this.editor()}
        </ModalComponent.Body>
        <ModalComponent.Actions>

Save

        </ModalComponent.Actions>
      </ModalComponent>
    );
  }
}
<|endoftext|>


<commit_before>
(function() {
'use strict';

Elm.Debugger = null;
Elm.debuggerAttach = function(module) {
  return {
    make: function(runtime) {
      var wrappedModule = debugModule(module, runtime);
      Elm.Debugger = debuggerInit(wrappedModule, runtime);
      return wrappedModule.moduleInstance;
    }
  };
};

function debugModule(module, runtime) {
  var programPaused = false;
  var recordedEvents = [];
  var eventCounter = 0;
  var asyncTimers = [];

  function wrapNotify(id, v) {
    var timestep = Date.now();

    if (programPaused) {
      // ignore notify because we are stepping
    }
    else {
      runtime.notify(id, v, timestep);
      recordEvent(id, v, timestep);
    }
  };

  function wrapRunDelayed(func, delayMs) {
    return safeSetTimeout(func, delayMs);
  };

  function safeSetTimeout(func, delayMs) {
    var timerId = setTimeout(func, delayMs);
    asyncTimers.push({ func:func, delayMs:delayMs, timerId:timerId });
    return timerId;
  }

  function recordEvent(id, v, timestep) {
    recordedEvents.push({ id:id, value:v, timestep:timestep });
  }

  function clearAsyncEvents() {
    asyncTimers.forEach(function(timer) {
      clearTimeout(timer.timerId);
    });
  }

  function clearRecordedEvents() {
    recordedEvents = [];
  }

  function getRecordedEvents() {
    return recordedEvents.slice();
  }

  function setPaused(v) {
    programPaused = v;
  }

  function getPaused() {
    return programPaused;
  }

  var wrappedRuntime = {
    notify: wrapNotify,
    runDelayed: wrapRunDelayed,
    node: runtime.node,
    display: runtime.display,
    id: runtime.id,
    addListener: runtime.addListener,
    inputs: runtime.inputs
  };

  var moduleInstance = module.make(wrappedRuntime);
  var moduleNodes = flattenNodes(wrappedRuntime.inputs);

  return {
    moduleInstance: moduleInstance,
    moduleNodes: moduleNodes,
    initialNodeValues: saveNodeValues(moduleNodes),
    initialAsyncTimers: asyncTimers.slice(),
    // API functions
    clearAsyncEvents: clearAsyncEvents,
    clearRecordedEvents: clearRecordedEvents,
    getRecordedEvents: getRecordedEvents,
    getPaused: getPaused,
    setPaused: setPaused
  };
}

function debuggerInit(debugModule, runtime) {
  var Signal = Elm.Signal.make(runtime);

  var tracePath = tracePathInit(runtime);
  var tracePathNode = A2(Signal.lift, tracePath.graphicsUpdate, debugModule.moduleInstance.main);
  runtime.node.parentNode.appendChild(tracePath.canvas);

  function resetProgram() {
    debugModule.clearAsyncEvents();
    restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
    redrawGraphics();
  }

  function restartProgram() {
    resetProgram();
    debugModule.clearRecordedEvents();
    debugModule.initialAsyncTimers.forEach(function(timer) {
      var func = timer.func;
      func();
    });
  }

  function stepTo(index) {
    if (!debugModule.getPaused()) {
      debugModule.setPaused(true);
      resetProgram();
    }

    if (index < 0 || index >= recordedEvents.length) {
      throw "Index out of bounds: " + index;
    }

    if (index < eventCounter) {
      resetProgram();
      eventCounter = 0;
    }

    assert(index >= eventCounter, "index must be bad");
    while (eventCounter < index) {
      var nextEvent = recordedEvents[eventCounter];
      runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);

      eventCounter += 1;
    }
    assert(eventCounter == index, "while loop didnt' work");
  }

  function getMaxSteps() {
    return recordedEvents.length - 1;
  }

  function doPlayback(eventList) {
    var x = eventList.shift();
    var time = x[2];
    runtime.notify(x[0], x[1], x[2]);

    if (eventList.length > 0) {
      var delta = eventList[0][2] - time;
      setTimeout(function() { doPlayback(eventList); }, delta);
    }
  }

  function redrawGraphics() {
    var graphicsNode = debugModule.moduleInstance.main.kids[0];
    graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
  }

  var elmDebugger = {
      reset: resetProgram,
      restart: restartProgram,
      getMaxSteps: getMaxSteps,
      stepTo: stepTo
  };

  return elmDebugger;
}

function Point(x, y) {
  this.x = x;
  this.y = y;

  this.translate = function(x, y) {
    return new Point(this.x + x, this.y + y);
  }

  this.equals = function(p) {
    return this.x == p.x && this.y == p.y;
  }
}

function tracePathInit(runtime) {
  var List = Elm.List.make(runtime);
  var tracePathCanvas = createCanvas();
  var tracePositions = {};

  function findPositions(currentScene) {
    var positions = {};
    function processElement(elem, offset) {
      if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
      {
        List.map(F2(processForm)(offset))(elem.element.model.forms);
      }
      else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
      {
        positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
      }
    }

    function processForm(offset, form) {
      if (form.form.ctor == "FElement")
      {
        processElement(form.form._0, offset.translate(form.x, -form.y));
      }
    }

    processElement(currentScene, new Point(0, 0));
    return positions;
  }

  function appendPositions(positions) {
    for (var id in positions) {
      var pos = positions[id];
      if (tracePositions.hasOwnProperty(id)) {
        var points = tracePositions[id];
        if (!pos.equals(points[points.length-1])) {
          points.push(pos);
        }
      }
      else {
        tracePositions[id] = [pos];
      }
    }
  }

  function graphicsUpdate(currentScene) {
    var ctx = tracePathCanvas.getContext('2d');
    ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);

    ctx.save();
    ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
    appendPositions(findPositions(currentScene));
    for (var id in tracePositions)
    {
      ctx.beginPath();
      var points = tracePositions[id];
      for (var i=0; i < points.length; i++)
      {
        var p = points[i];
        if (i == 0) {
          ctx.moveTo(p.x, p.y);
        }
        else {
          ctx.lineTo(p.x, p.y);
        }
      }
    }
    ctx.lineWidth = 1;
    ctx.strokeStyle = "black";
    ctx.stroke();

    ctx.restore();
  }

  function clearTraces() {
    tracePositions = {};
  }

  return {
    graphicsUpdate: graphicsUpdate,
    canvas: tracePathCanvas,
    clearTraces: clearTraces
  }
}

function createCanvas() {
  var c = document.createElement('canvas');
  c.width = window.innerWidth;
  c.height = window.innerHeight;
  c.style.position = "absolute";
  c.style.top = "0";
  c.style.left = "0";
  return c;
}

function assert(bool, msg) {
  if (!bool) {
    throw "Assertion error: " + msg;
  }
}

function saveNodeValues(allNodes) {
  var nodeValues = [];

  allNodes.forEach(function(node) {
    nodeValues.push({ value: node.value, id: node.id });
  });

  return nodeValues;
};

function restoreNodeValues(allNodes, nodeStates) {
  assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
  for (var i=0; i < allNodes.length; i++) {
    var node = allNodes[i];
    var state = nodeStates[i];
    assert(node.id == state.id, "the nodes moved position");

    node.value = state.value;
  }
}

function flattenNodes(nodes) {
  var treeReduce = function(prev, cur, i, arr) {
    var flatKids = cur.kids.reduce(treeReduce, []);
    flatKids.push(cur);
    return prev.concat(flatKids);
  }

  return nodes.reduce(treeReduce, []);
};

}());
<commit_msg>Debugger: use onload and postMessage to allow debugger to reload, and still connect with UI.<commit_after>
(function() {
'use strict';

Elm.Debugger = null;
Elm.debuggerAttach = function(module) {
  return {
    make: function(runtime) {
      var wrappedModule = debugModule(module, runtime);
      Elm.Debugger = debuggerInit(wrappedModule, runtime);
      if (parent.window) {
        var dispatch = function(_) {
          parent.window.postMessage("elmDebuggerInit", window.location.origin);
        };
        if (parent.window.document.readyState === "complete") {
          dispatch();
        }
        else {
          parent.window.addEventListener("load", dispatch);
        }
      }
      return wrappedModule.moduleInstance;
    }
  };
};

function debugModule(module, runtime) {
  var programPaused = false;
  var recordedEvents = [];
  var eventCounter = 0;
  var asyncTimers = [];

  function wrapNotify(id, v) {
    var timestep = Date.now();

    if (programPaused) {
      // ignore notify because we are stepping
    }
    else {
      runtime.notify(id, v, timestep);
      recordEvent(id, v, timestep);
    }
  };

  function wrapRunDelayed(func, delayMs) {
    return safeSetTimeout(func, delayMs);
  };

  function safeSetTimeout(func, delayMs) {
    var timerId = setTimeout(func, delayMs);
    asyncTimers.push({ func:func, delayMs:delayMs, timerId:timerId });
    return timerId;
  }

  function recordEvent(id, v, timestep) {
    recordedEvents.push({ id:id, value:v, timestep:timestep });
  }

  function clearAsyncEvents() {
    asyncTimers.forEach(function(timer) {
      clearTimeout(timer.timerId);
    });
  }

  function clearRecordedEvents() {
    recordedEvents = [];
  }

  function getRecordedEvents() {
    return recordedEvents.slice();
  }

  function setPaused(v) {
    programPaused = v;
  }

  function getPaused() {
    return programPaused;
  }

  var wrappedRuntime = {
    notify: wrapNotify,
    runDelayed: wrapRunDelayed,
    node: runtime.node,
    display: runtime.display,
    id: runtime.id,
    addListener: runtime.addListener,
    inputs: runtime.inputs
  };

  var moduleInstance = module.make(wrappedRuntime);
  var moduleNodes = flattenNodes(wrappedRuntime.inputs);

  return {
    moduleInstance: moduleInstance,
    moduleNodes: moduleNodes,
    initialNodeValues: saveNodeValues(moduleNodes),
    initialAsyncTimers: asyncTimers.slice(),
    // API functions
    clearAsyncEvents: clearAsyncEvents,
    clearRecordedEvents: clearRecordedEvents,
    getRecordedEvents: getRecordedEvents,
    getPaused: getPaused,
    setPaused: setPaused
  };
}

function debuggerInit(debugModule, runtime) {
  var Signal = Elm.Signal.make(runtime);

  var tracePath = tracePathInit(runtime);
  var tracePathNode = A2(Signal.lift, tracePath.graphicsUpdate, debugModule.moduleInstance.main);
  runtime.node.parentNode.appendChild(tracePath.canvas);

  function resetProgram() {
    debugModule.clearAsyncEvents();
    restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
    redrawGraphics();
  }

  function restartProgram() {
    resetProgram();
    debugModule.clearRecordedEvents();
    debugModule.initialAsyncTimers.forEach(function(timer) {
      var func = timer.func;
      func();
    });
  }

  function stepTo(index) {
    if (!debugModule.getPaused()) {
      debugModule.setPaused(true);
      resetProgram();
    }

    if (index < 0 || index >= recordedEvents.length) {
      throw "Index out of bounds: " + index;
    }

    if (index < eventCounter) {
      resetProgram();
      eventCounter = 0;
    }

    assert(index >= eventCounter, "index must be bad");
    while (eventCounter < index) {
      var nextEvent = recordedEvents[eventCounter];
      runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);

      eventCounter += 1;
    }
    assert(eventCounter == index, "while loop didnt' work");
  }

  function getMaxSteps() {
    return recordedEvents.length - 1;
  }

  function doPlayback(eventList) {
    var x = eventList.shift();
    var time = x[2];
    runtime.notify(x[0], x[1], x[2]);

    if (eventList.length > 0) {
      var delta = eventList[0][2] - time;
      setTimeout(function() { doPlayback(eventList); }, delta);
    }
  }

  function redrawGraphics() {
    var graphicsNode = debugModule.moduleInstance.main.kids[0];
    graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
  }

  var elmDebugger = {
      reset: resetProgram,
      restart: restartProgram,
      getMaxSteps: getMaxSteps,
      stepTo: stepTo
  };

  return elmDebugger;
}

function Point(x, y) {
  this.x = x;
  this.y = y;

  this.translate = function(x, y) {
    return new Point(this.x + x, this.y + y);
  }

  this.equals = function(p) {
    return this.x == p.x && this.y == p.y;
  }
}

function tracePathInit(runtime) {
  var List = Elm.List.make(runtime);
  var tracePathCanvas = createCanvas();
  var tracePositions = {};

  function findPositions(currentScene) {
    var positions = {};
    function processElement(elem, offset) {
      if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
      {
        List.map(F2(processForm)(offset))(elem.element.model.forms);
      }
      else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
      {
        positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
      }
    }

    function processForm(offset, form) {
      if (form.form.ctor == "FElement")
      {
        processElement(form.form._0, offset.translate(form.x, -form.y));
      }
    }

    processElement(currentScene, new Point(0, 0));
    return positions;
  }

  function appendPositions(positions) {
    for (var id in positions) {
      var pos = positions[id];
      if (tracePositions.hasOwnProperty(id)) {
        var points = tracePositions[id];
        if (!pos.equals(points[points.length-1])) {
          points.push(pos);
        }
      }
      else {
        tracePositions[id] = [pos];
      }
    }
  }

  function graphicsUpdate(currentScene) {
    var ctx = tracePathCanvas.getContext('2d');
    ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);

    ctx.save();
    ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
    appendPositions(findPositions(currentScene));
    for (var id in tracePositions)
    {
      ctx.beginPath();
      var points = tracePositions[id];
      for (var i=0; i < points.length; i++)
      {
        var p = points[i];
        if (i == 0) {
          ctx.moveTo(p.x, p.y);
        }
        else {
          ctx.lineTo(p.x, p.y);
        }
      }
    }
    ctx.lineWidth = 1;
    ctx.strokeStyle = "black";
    ctx.stroke();

    ctx.restore();
  }

  function clearTraces() {
    tracePositions = {};
  }

  return {
    graphicsUpdate: graphicsUpdate,
    canvas: tracePathCanvas,
    clearTraces: clearTraces
  }
}

function createCanvas() {
  var c = document.createElement('canvas');
  c.width = window.innerWidth;
  c.height = window.innerHeight;
  c.style.position = "absolute";
  c.style.top = "0";
  c.style.left = "0";
  return c;
}

function assert(bool, msg) {
  if (!bool) {
    throw "Assertion error: " + msg;
  }
}

function saveNodeValues(allNodes) {
  var nodeValues = [];

  allNodes.forEach(function(node) {
    nodeValues.push({ value: node.value, id: node.id });
  });

  return nodeValues;
};

function restoreNodeValues(allNodes, nodeStates) {
  assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
  for (var i=0; i < allNodes.length; i++) {
    var node = allNodes[i];
    var state = nodeStates[i];
    assert(node.id == state.id, "the nodes moved position");

    node.value = state.value;
  }
}

function flattenNodes(nodes) {
  var treeReduce = function(prev, cur, i, arr) {
    var flatKids = cur.kids.reduce(treeReduce, []);
    flatKids.push(cur);
    return prev.concat(flatKids);
  }

  return nodes.reduce(treeReduce, []);
};

}());
<|endoftext|>


<commit_before>/*
 * Copyright (c) 1997-2013, SalesLogix, NA., LLC. All rights reserved.
 */

/**
 * @class Mobile.SalesLogix.Views.Attachments.List
 *
 * @extends Sage.Platform.Mobile.List
 * @mixins Sage.Platform.Mobile.List
 * @mixins Mobile.SalesLogix.Views._RightDrawerListMixin
 * @mixins Mobile.SalesLogix.Views._CardLayoutListMixin
 * @mixins Sage.Platform.Mobile._LegacySDataListMixin
 *
 * @requires Sage.Platform.Mobile.List
 * @requires Sage.Platform.Mobile._LegacySDataListMixin
 * @requires Sage.Platform.Mobile.Convert
 *
 * @requires Mobile.SalesLogix.Format
 * @requires Mobile.SalesLogix.Views._RightDrawerListMixin
 * @requires Mobile.SalesLogix.Views._CardLayoutListMixin
 *
 * @requires moment
 *
 */
define('Mobile/SalesLogix/Views/Attachment/List', [
    'dojo/_base/declare',
    'dojo/string',
    'dojo/has',
    'Mobile/SalesLogix/Format',
    'Mobile/SalesLogix/Utility',
    'Sage/Platform/Mobile/List',
    'Sage/Platform/Mobile/_LegacySDataListMixin',
    'Sage/Platform/Mobile/Convert',
    '../_RightDrawerListMixin',
    '../_CardLayoutListMixin',
    'moment'
], function(
    declare,
    string,
    has,
    format,
    utility,
    List,
    _LegacySDataListMixin,
    convert,
    _RightDrawerListMixin,
    _CardLayoutListMixin,
    moment
) {

    return declare('Mobile.SalesLogix.Views.Attachment.List', [List, _RightDrawerListMixin, _CardLayoutListMixin, _LegacySDataListMixin], {
        //Templates
        itemTemplate: new Simplate([
           '{% if ($.dataType === "R") { %}',
               '{%! $$.fileTemplate %}',
           '{% } else { %}',
              '{%! $$.urlTemplate %}',
           '{% } %}'
        ]),
        fileTemplate: new Simplate([
             '
{%: $.description %}
',
             '
({%: $$.uploadedOnText %} {%: Mobile.SalesLogix.Format.relativeDate($.attachDate) %}) ',
            '{%: Mobile.SalesLogix.Format.fileSize($.fileSize) %}
',
            '
{%: Mobile.SalesLogix.Utility.getFileExtension($.fileName) %}
',
            '{% if($.user) { %}',
                '
{%: $.user.$descriptor  %}
',
            '{% } %}'
        ]),
        urlTemplate: new Simplate([
            '
{%: $.description %}
',
            '{% if ($.attachDate) { %}',
                '
({%: $$.uploadedOnText %} {%: Mobile.SalesLogix.Format.relativeDate($.attachDate) %})
',
            '{% } %}',
            '
{%: $.url %}
',
            '{% if($.user) { %}',
            '
{%: $.user.$descriptor  %}
',
            '{% } %}'
        ]),

        //Localization
        titleText: 'Attachments',
        attachmentDateFormatText: 'ddd M/D/YYYY hh:mm:ss',
        uploadedOnText: 'Uploaded ',// Uploaded 10 days ago

        //View Properties
        id: 'attachment_list',
        security: null,
        enableActions: true,
        detailView: 'view_attachment',
        insertView: 'attachment_Add',
        iconClass: 'fa fa-paperclip fa-lg',
        queryOrderBy: 'attachDate desc',
        querySelect:  [
            'description',
            'user',
            'createUser',
            'attachDate',
            'fileSize',
            'fileName',
            'url',
            'fileExists',
            'remoteStatus',
            'dataType',
            'ModifyDate'
        ],
        resourceKind: 'attachments',
        contractName: 'system',
        queryInclude: ['$descriptors'],

        hashTagQueries: {
            'url': 'url ne null',
            'binary': 'url eq null'
        },
        hashTagQueriesText: {
            'url': 'url',
            'binary': 'binary'
        },
        createToolLayout: function() {
            if (!has('html5-file-api')) {
                this.insertView = null;
            } else {
                return this.inherited(arguments);
            }
        },
        createRequest: function() {
            var request = this.inherited(arguments);
            request.setQueryArg('_includeFile', 'false');
            return request;
        },
        formatSearchQuery: function(searchQuery) {
            return string.substitute('upper(description) like "%${0}%"', [this.escapeSearchQuery(searchQuery.toUpperCase())]);
        },
        getLink: function(attachment) {
            if (attachment['url']) {
                var href = attachment['url'] || '';
                href = (href.indexOf('http') < 0) ? 'http://' + href : href;
                return string.substitute('${2}', [href, attachment['url'], attachment['$descriptor']]);
            } else {
                if (attachment['fileExists']) {
                    return string.substitute('${1}',
                        [attachment['$key'], attachment['$descriptor']]);
                } else {
                    return attachment['$descriptor'];
                }
            }
        },
        itemIconClass: 'fa-file-o',
        fileIconByType: {
            'xls': 'fa-file-excel-o',
            'xlsx': 'fa-file-excel-o',
            'doc': 'fa-file-word-o',
            'docx': 'fa-file-word-o',
            'ppt': 'fa-file-powerpoint-o',
            'pptx': 'fa-file-powerpoint-o',
            'txt': 'fa-file-text-o',
            'rtf': 'fa-file-text-o',
            'csv': 'fa-file-text-o',
            'pdf': 'fa-file-pdf-o',
            'zip': 'fa-file-zip-o',
            'png': 'fa-file-image-o',
            'jpg': 'fa-file-image-o',
            'gif': 'fa-file-image-o',
            'bmp': 'fa-file-image-o'
        },
        getItemIconClass: function(entry) {
            var cls, typeCls, type, fileName = entry && entry.fileName;
            type = utility.getFileExtension(fileName);
            cls = this.itemIconClass;
            if (type) {
                type = type.substr(1); //Remove the '.' from the ext.
                typeCls = this.fileIconByType[type];
                if (typeCls) {
                    cls = typeCls;
                }
            }
            if (cls) {
                cls = 'fa ' + cls + ' fa-2x';
            }
            return cls;
        },
        createIndicatorLayout: function() {
            return this.itemIndicators || (this.itemIndicators = [{
                id: 'touched',
                cls: 'fa fa-hand-o-up',
                label: 'Touched',
                onApply: function(entry, parent) {
                    this.isEnabled = parent.hasBeenTouched(entry);
                }
            }]
            );
        },
        hasBeenTouched: function(entry) {
            var modifiedDate, currentDate, weekAgo;
            if (entry['modifyDate']) {
                modifiedDate = moment(convert.toDateFromString(entry['modifyDate']));
                currentDate = moment().endOf('day');
                weekAgo = moment().subtract(1, 'weeks');

                return modifiedDate.isAfter(weekAgo) &&
                    modifiedDate.isBefore(currentDate);
            }
            return false;
        }
    });
});

<commit_msg>Fixed #MBL-10790 Attachment Indicator size<commit_after>/*
 * Copyright (c) 1997-2013, SalesLogix, NA., LLC. All rights reserved.
 */

/**
 * @class Mobile.SalesLogix.Views.Attachments.List
 *
 * @extends Sage.Platform.Mobile.List
 * @mixins Sage.Platform.Mobile.List
 * @mixins Mobile.SalesLogix.Views._RightDrawerListMixin
 * @mixins Mobile.SalesLogix.Views._CardLayoutListMixin
 * @mixins Sage.Platform.Mobile._LegacySDataListMixin
 *
 * @requires Sage.Platform.Mobile.List
 * @requires Sage.Platform.Mobile._LegacySDataListMixin
 * @requires Sage.Platform.Mobile.Convert
 *
 * @requires Mobile.SalesLogix.Format
 * @requires Mobile.SalesLogix.Views._RightDrawerListMixin
 * @requires Mobile.SalesLogix.Views._CardLayoutListMixin
 *
 * @requires moment
 *
 */
define('Mobile/SalesLogix/Views/Attachment/List', [
    'dojo/_base/declare',
    'dojo/string',
    'dojo/has',
    'Mobile/SalesLogix/Format',
    'Mobile/SalesLogix/Utility',
    'Sage/Platform/Mobile/List',
    'Sage/Platform/Mobile/_LegacySDataListMixin',
    'Sage/Platform/Mobile/Convert',
    '../_RightDrawerListMixin',
    '../_CardLayoutListMixin',
    'moment'
], function(
    declare,
    string,
    has,
    format,
    utility,
    List,
    _LegacySDataListMixin,
    convert,
    _RightDrawerListMixin,
    _CardLayoutListMixin,
    moment
) {

    return declare('Mobile.SalesLogix.Views.Attachment.List', [List, _RightDrawerListMixin, _CardLayoutListMixin, _LegacySDataListMixin], {
        //Templates
        itemTemplate: new Simplate([
           '{% if ($.dataType === "R") { %}',
               '{%! $$.fileTemplate %}',
           '{% } else { %}',
              '{%! $$.urlTemplate %}',
           '{% } %}'
        ]),
        fileTemplate: new Simplate([
             '
{%: $.description %}
',
             '
({%: $$.uploadedOnText %} {%: Mobile.SalesLogix.Format.relativeDate($.attachDate) %}) ',
            '{%: Mobile.SalesLogix.Format.fileSize($.fileSize) %}
',
            '
{%: Mobile.SalesLogix.Utility.getFileExtension($.fileName) %}
',
            '{% if($.user) { %}',
                '
{%: $.user.$descriptor  %}
',
            '{% } %}'
        ]),
        urlTemplate: new Simplate([
            '
{%: $.description %}
',
            '{% if ($.attachDate) { %}',
                '
({%: $$.uploadedOnText %} {%: Mobile.SalesLogix.Format.relativeDate($.attachDate) %})
',
            '{% } %}',
            '
{%: $.url %}
',
            '{% if($.user) { %}',
            '
{%: $.user.$descriptor  %}
',
            '{% } %}'
        ]),

        //Localization
        titleText: 'Attachments',
        attachmentDateFormatText: 'ddd M/D/YYYY hh:mm:ss',
        uploadedOnText: 'Uploaded ',// Uploaded 10 days ago

        //View Properties
        id: 'attachment_list',
        security: null,
        enableActions: true,
        detailView: 'view_attachment',
        insertView: 'attachment_Add',
        iconClass: 'fa fa-paperclip fa-lg',
        queryOrderBy: 'attachDate desc',
        querySelect:  [
            'description',
            'user',
            'createUser',
            'attachDate',
            'fileSize',
            'fileName',
            'url',
            'fileExists',
            'remoteStatus',
            'dataType',
            'ModifyDate'
        ],
        resourceKind: 'attachments',
        contractName: 'system',
        queryInclude: ['$descriptors'],

        hashTagQueries: {
            'url': 'url ne null',
            'binary': 'url eq null'
        },
        hashTagQueriesText: {
            'url': 'url',
            'binary': 'binary'
        },
        createToolLayout: function() {
            if (!has('html5-file-api')) {
                this.insertView = null;
            } else {
                return this.inherited(arguments);
            }
        },
        createRequest: function() {
            var request = this.inherited(arguments);
            request.setQueryArg('_includeFile', 'false');
            return request;
        },
        formatSearchQuery: function(searchQuery) {
            return string.substitute('upper(description) like "%${0}%"', [this.escapeSearchQuery(searchQuery.toUpperCase())]);
        },
        getLink: function(attachment) {
            if (attachment['url']) {
                var href = attachment['url'] || '';
                href = (href.indexOf('http') < 0) ? 'http://' + href : href;
                return string.substitute('${2}', [href, attachment['url'], attachment['$descriptor']]);
            } else {
                if (attachment['fileExists']) {
                    return string.substitute('${1}',
                        [attachment['$key'], attachment['$descriptor']]);
                } else {
                    return attachment['$descriptor'];
                }
            }
        },
        itemIconClass: 'fa-file-o',
        fileIconByType: {
            'xls': 'fa-file-excel-o',
            'xlsx': 'fa-file-excel-o',
            'doc': 'fa-file-word-o',
            'docx': 'fa-file-word-o',
            'ppt': 'fa-file-powerpoint-o',
            'pptx': 'fa-file-powerpoint-o',
            'txt': 'fa-file-text-o',
            'rtf': 'fa-file-text-o',
            'csv': 'fa-file-text-o',
            'pdf': 'fa-file-pdf-o',
            'zip': 'fa-file-zip-o',
            'png': 'fa-file-image-o',
            'jpg': 'fa-file-image-o',
            'gif': 'fa-file-image-o',
            'bmp': 'fa-file-image-o'
        },
        getItemIconClass: function(entry) {
            var cls, typeCls, type, fileName = entry && entry.fileName;
            type = utility.getFileExtension(fileName);
            cls = this.itemIconClass;
            if (type) {
                type = type.substr(1); //Remove the '.' from the ext.
                typeCls = this.fileIconByType[type];
                if (typeCls) {
                    cls = typeCls;
                }
            }
            if (cls) {
                cls = 'fa ' + cls + ' fa-2x';
            }
            return cls;
        },
        createIndicatorLayout: function() {
            return this.itemIndicators || (this.itemIndicators = [{
                id: 'touched',
                cls: 'fa fa-hand-o-up fa-lg',
                label: 'Touched',
                onApply: function(entry, parent) {
                    this.isEnabled = parent.hasBeenTouched(entry);
                }
            }]
            );
        },
        hasBeenTouched: function(entry) {
            var modifiedDate, currentDate, weekAgo;
            if (entry['modifyDate']) {
                modifiedDate = moment(convert.toDateFromString(entry['modifyDate']));
                currentDate = moment().endOf('day');
                weekAgo = moment().subtract(1, 'weeks');

                return modifiedDate.isAfter(weekAgo) &&
                    modifiedDate.isBefore(currentDate);
            }
            return false;
        }
    });
});

<|endoftext|>


<commit_before>// Copyright 2012 The Obvious Corporation.

/*
 * This simply fetches the right version of phantom for the current platform.
 */

'use strict'

var AdmZip = require('adm-zip')
var cp = require('child_process')
var fs = require('fs')
var helper = require('./lib/phantomjs')
var http = require('http')
var kew = require('kew')
var ncp = require('ncp')
var npmconf = require('npmconf')
var mkdirp = require('mkdirp')
var path = require('path')
var rimraf = require('rimraf').sync
var url = require('url')
var util = require('util')
var which = require('which')

var downloadUrl = 'http://phantomjs.googlecode.com/files/phantomjs-' + helper.version + '-'

var originalPath = process.env.PATH

// NPM adds bin directories to the path, which will cause `which` to find the
// bin for this package not the actual phantomjs bin.  Also help out people who
// put ./bin on their path
process.env.PATH = helper.cleanPath(originalPath)

var libPath = path.join(__dirname, 'lib')
var pkgPath = path.join(libPath, 'phantom')
var phantomPath = null
var tmpPath = null

var whichDeferred = kew.defer()
which('phantomjs', whichDeferred.makeNodeResolver())
whichDeferred.promise
  .then(function (path) {
    phantomPath = path

    // Horrible hack to avoid problems during global install. We check to see if
    // the file `which` found is our own bin script.
    // See: https://github.com/Obvious/phantomjs/issues/85
    if (/NPM_INSTALL_MARKER/.test(fs.readFileSync(phantomPath, 'utf8'))) {
      console.log('Looks like an `npm install -g`; unable to check for already installed version.')
      throw new Error('Global install')

    } else {
      var checkVersionDeferred = kew.defer()
      cp.execFile(phantomPath, ['--version'], checkVersionDeferred.makeNodeResolver())
      return checkVersionDeferred.promise
    }
  })
  .then(function (stdout) {
    var version = stdout.trim()
    if (helper.version == version) {
      writeLocationFile(phantomPath)
      console.log('PhantomJS is already installed at', phantomPath + '.')
      exit(0)

    } else {
      console.log('PhantomJS detected, but wrong version', stdout.trim(), '@', phantomPath + '.')
      throw new Error('Wrong version')
    }
  })
  .fail(function (err) {
    // Trying to use a local file failed, so initiate download and install
    // steps instead.
    var npmconfDeferred = kew.defer()
    npmconf.load(npmconfDeferred.makeNodeResolver())
    return npmconfDeferred.promise
  })
  .then(function (conf) {
    tmpPath = findSuitableTempDirectory(conf)

    // Can't use a global version so start a download.
    if (process.platform === 'linux' && process.arch === 'x64') {
      downloadUrl += 'linux-x86_64.tar.bz2'
    } else if (process.platform === 'linux') {
      downloadUrl += 'linux-i686.tar.bz2'
    } else if (process.platform === 'darwin' || process.platform === 'openbsd') {
      downloadUrl += 'macosx.zip'
    } else if (process.platform === 'win32') {
      downloadUrl += 'windows.zip'
    } else {
      console.log('Unexpected platform or architecture:', process.platform, process.arch)
      exit(1)
    }

    var fileName = downloadUrl.split('/').pop()
    var downloadedFile = path.join(tmpPath, fileName)

    // Start the install.
    if (!fs.existsSync(downloadedFile)) {
      console.log('Downloading', downloadUrl)
      console.log('Saving to', downloadedFile)
      return requestBinary(getRequestOptions(conf.get('proxy')), downloadedFile)
    } else {
      console.log('Download already available at', downloadedFile)
      return downloadedFile
    }
  })
  .then(function (downloadedFile) {
    return extractDownload(downloadedFile)
  })
  .then(function (extractedPath) {
    return copyIntoPlace(extractedPath, pkgPath)
  })
  .then(function () {
    var location = process.platform === 'win32' ?
        path.join(pkgPath, 'phantomjs.exe') :
        path.join(pkgPath, 'bin' ,'phantomjs')
    var relativeLocation = path.relative(libPath, location)
    writeLocationFile(relativeLocation)
    console.log('Done. Phantomjs binary available at', location)
    exit(0)
  })
  .fail(function (err) {
    console.error('Phantom installation failed', err, err.stack)
    exit(1)
  })

function writeLocationFile(location) {
  console.log('Writing location.js file')
  if (process.platform === 'win32') {
    location = location.replace(/\\/g, '\\\\')
  }
  fs.writeFileSync(path.join(libPath, 'location.js'),
      'module.exports.location = "' + location + '"')
}

function exit(code) {
  process.env.PATH = originalPath
  process.exit(code || 0)
}

function findSuitableTempDirectory(npmConf) {
  var now = Date.now()
  var candidateTmpDirs = [
    process.env.TMPDIR || '/tmp',
    npmConf.get('tmp'),
    path.join(process.cwd(), 'tmp')
  ]

  for (var i = 0; i < candidateTmpDirs.length; i++) {
    var candidatePath = path.join(candidateTmpDirs[i], 'phantomjs')

    try {
      mkdirp.sync(candidatePath, '0777')
      // Make double sure we have 0777 permissions; some operating systems
      // default umask does not allow write by default.
      fs.chmodSync(candidatePath, '0777')
      var testFile = path.join(candidatePath, now + '.tmp')
      fs.writeFileSync(testFile, 'test')
      fs.unlinkSync(testFile)
      return candidatePath
    } catch (e) {
      console.log(candidatePath, 'is not writable:', e.message)
    }
  }

  console.error('Can not find a writable tmp directory, please report issue ' +
      'on https://github.com/Obvious/phantomjs/issues/59 with as much ' +
      'information as possible.')
  exit(1)
}

function getRequestOptions(proxyUrl) {
  if (proxyUrl) {
    var options = url.parse(proxyUrl)
    options.path = downloadUrl
    options.headers = { Host: url.parse(downloadUrl).host }
    // If going through proxy, spoof the User-Agent, since may commerical proxies block blank or unknown agents in headers
    options.headers['User-Agent'] = 'curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5'
    // Turn basic authorization into proxy-authorization.
    if (options.auth) {
      options.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(options.auth).toString('base64')
      delete options.auth
    }

    return options
  } else {
    return url.parse(downloadUrl)
  }
}

function requestBinary(requestOptions, filePath) {
  var deferred = kew.defer()

  var count = 0
  var notifiedCount = 0
  var writePath = filePath + '-download-' + Date.now()
  var outFile = fs.openSync(writePath, 'w')

  var client = http.get(requestOptions, function (response) {
    var status = response.statusCode
    console.log('Receiving...')

    if (status === 200) {
      response.addListener('data',   function (data) {
        fs.writeSync(outFile, data, 0, data.length, null)
        count += data.length
        if ((count - notifiedCount) > 800000) {
          console.log('Received ' + Math.floor(count / 1024) + 'K...')
          notifiedCount = count
        }
      })

      response.addListener('end',   function () {
        console.log('Received ' + Math.floor(count / 1024) + 'K total.')
        fs.closeSync(outFile)
        fs.renameSync(writePath, filePath)
        deferred.resolve(filePath)
      })

    } else {
      client.abort()
      console.error('Error requesting archive')
      deferred.reject(new Error('Error with http request: ' + util.inspect(response.headers)))
    }
  })

  return deferred.promise
}

function extractDownload(filePath) {
  var deferred = kew.defer()
  // extract to a unique directory in case multiple processes are
  // installing and extracting at once
  var extractedPath = filePath + '-extract-' + Date.now()
  var options = {cwd: extractedPath}

  mkdirp.sync(extractedPath, '0777')
  // Make double sure we have 0777 permissions; some operating systems
  // default umask does not allow write by default.
  fs.chmodSync(extractedPath, '0777')

  if (filePath.substr(-4) === '.zip') {
    console.log('Extracting zip contents')

    try {
      var zip = new AdmZip(filePath)
      zip.extractAllTo(extractedPath, true)
      deferred.resolve(extractedPath)
    } catch (err) {
      console.error('Error extracting archive')
      deferred.reject(err)
    }

  } else {
    console.log('Extracting tar contents (via spawned process)')
    cp.execFile('tar', ['jxf', filePath], options, function (err, stdout, stderr) {
      if (err) {
        console.error('Error extracting archive')
        deferred.reject(err)
      } else {
        deferred.resolve(extractedPath)
      }
    })
  }
  return deferred.promise
}

function copyIntoPlace(extractedPath, targetPath) {
  rimraf(targetPath)

  var deferred = kew.defer()
  // Look for the extracted directory, so we can rename it.
  var files = fs.readdirSync(extractedPath)
  for (var i = 0; i < files.length; i++) {
    var file = path.join(extractedPath, files[i])
    if (fs.statSync(file).isDirectory() && file.indexOf(helper.version) != -1) {
      console.log('Renaming extracted folder', file, '->', targetPath)
      ncp(file, targetPath, deferred.makeNodeResolver())
      break
    }
  }

  // Cleanup extracted directory after it's been copied
  return deferred.promise.then(function() {
    return rimraf(extractedPath)
  });
}
<commit_msg>Correct env TEMP var for Windows<commit_after>// Copyright 2012 The Obvious Corporation.

/*
 * This simply fetches the right version of phantom for the current platform.
 */

'use strict'

var AdmZip = require('adm-zip')
var cp = require('child_process')
var fs = require('fs')
var helper = require('./lib/phantomjs')
var http = require('http')
var kew = require('kew')
var ncp = require('ncp')
var npmconf = require('npmconf')
var mkdirp = require('mkdirp')
var path = require('path')
var rimraf = require('rimraf').sync
var url = require('url')
var util = require('util')
var which = require('which')

var downloadUrl = 'http://phantomjs.googlecode.com/files/phantomjs-' + helper.version + '-'

var originalPath = process.env.PATH

// NPM adds bin directories to the path, which will cause `which` to find the
// bin for this package not the actual phantomjs bin.  Also help out people who
// put ./bin on their path
process.env.PATH = helper.cleanPath(originalPath)

var libPath = path.join(__dirname, 'lib')
var pkgPath = path.join(libPath, 'phantom')
var phantomPath = null
var tmpPath = null

var whichDeferred = kew.defer()
which('phantomjs', whichDeferred.makeNodeResolver())
whichDeferred.promise
  .then(function (path) {
    phantomPath = path

    // Horrible hack to avoid problems during global install. We check to see if
    // the file `which` found is our own bin script.
    // See: https://github.com/Obvious/phantomjs/issues/85
    if (/NPM_INSTALL_MARKER/.test(fs.readFileSync(phantomPath, 'utf8'))) {
      console.log('Looks like an `npm install -g`; unable to check for already installed version.')
      throw new Error('Global install')

    } else {
      var checkVersionDeferred = kew.defer()
      cp.execFile(phantomPath, ['--version'], checkVersionDeferred.makeNodeResolver())
      return checkVersionDeferred.promise
    }
  })
  .then(function (stdout) {
    var version = stdout.trim()
    if (helper.version == version) {
      writeLocationFile(phantomPath)
      console.log('PhantomJS is already installed at', phantomPath + '.')
      exit(0)

    } else {
      console.log('PhantomJS detected, but wrong version', stdout.trim(), '@', phantomPath + '.')
      throw new Error('Wrong version')
    }
  })
  .fail(function (err) {
    // Trying to use a local file failed, so initiate download and install
    // steps instead.
    var npmconfDeferred = kew.defer()
    npmconf.load(npmconfDeferred.makeNodeResolver())
    return npmconfDeferred.promise
  })
  .then(function (conf) {
    tmpPath = findSuitableTempDirectory(conf)

    // Can't use a global version so start a download.
    if (process.platform === 'linux' && process.arch === 'x64') {
      downloadUrl += 'linux-x86_64.tar.bz2'
    } else if (process.platform === 'linux') {
      downloadUrl += 'linux-i686.tar.bz2'
    } else if (process.platform === 'darwin' || process.platform === 'openbsd') {
      downloadUrl += 'macosx.zip'
    } else if (process.platform === 'win32') {
      downloadUrl += 'windows.zip'
    } else {
      console.log('Unexpected platform or architecture:', process.platform, process.arch)
      exit(1)
    }

    var fileName = downloadUrl.split('/').pop()
    var downloadedFile = path.join(tmpPath, fileName)

    // Start the install.
    if (!fs.existsSync(downloadedFile)) {
      console.log('Downloading', downloadUrl)
      console.log('Saving to', downloadedFile)
      return requestBinary(getRequestOptions(conf.get('proxy')), downloadedFile)
    } else {
      console.log('Download already available at', downloadedFile)
      return downloadedFile
    }
  })
  .then(function (downloadedFile) {
    return extractDownload(downloadedFile)
  })
  .then(function (extractedPath) {
    return copyIntoPlace(extractedPath, pkgPath)
  })
  .then(function () {
    var location = process.platform === 'win32' ?
        path.join(pkgPath, 'phantomjs.exe') :
        path.join(pkgPath, 'bin' ,'phantomjs')
    var relativeLocation = path.relative(libPath, location)
    writeLocationFile(relativeLocation)
    console.log('Done. Phantomjs binary available at', location)
    exit(0)
  })
  .fail(function (err) {
    console.error('Phantom installation failed', err, err.stack)
    exit(1)
  })

function writeLocationFile(location) {
  console.log('Writing location.js file')
  if (process.platform === 'win32') {
    location = location.replace(/\\/g, '\\\\')
  }
  fs.writeFileSync(path.join(libPath, 'location.js'),
      'module.exports.location = "' + location + '"')
}

function exit(code) {
  process.env.PATH = originalPath
  process.exit(code || 0)
}

function findSuitableTempDirectory(npmConf) {
  var now = Date.now()
  var candidateTmpDirs = [
    process.env.TMPDIR || process.env.TEMP || '/tmp',
    npmConf.get('tmp'),
    path.join(process.cwd(), 'tmp')
  ]

  for (var i = 0; i < candidateTmpDirs.length; i++) {
    var candidatePath = path.join(candidateTmpDirs[i], 'phantomjs')

    try {
      mkdirp.sync(candidatePath, '0777')
      // Make double sure we have 0777 permissions; some operating systems
      // default umask does not allow write by default.
      fs.chmodSync(candidatePath, '0777')
      var testFile = path.join(candidatePath, now + '.tmp')
      fs.writeFileSync(testFile, 'test')
      fs.unlinkSync(testFile)
      return candidatePath
    } catch (e) {
      console.log(candidatePath, 'is not writable:', e.message)
    }
  }

  console.error('Can not find a writable tmp directory, please report issue ' +
      'on https://github.com/Obvious/phantomjs/issues/59 with as much ' +
      'information as possible.')
  exit(1)
}

function getRequestOptions(proxyUrl) {
  if (proxyUrl) {
    var options = url.parse(proxyUrl)
    options.path = downloadUrl
    options.headers = { Host: url.parse(downloadUrl).host }
    // If going through proxy, spoof the User-Agent, since may commerical proxies block blank or unknown agents in headers
    options.headers['User-Agent'] = 'curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5'
    // Turn basic authorization into proxy-authorization.
    if (options.auth) {
      options.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(options.auth).toString('base64')
      delete options.auth
    }

    return options
  } else {
    return url.parse(downloadUrl)
  }
}

function requestBinary(requestOptions, filePath) {
  var deferred = kew.defer()

  var count = 0
  var notifiedCount = 0
  var writePath = filePath + '-download-' + Date.now()
  var outFile = fs.openSync(writePath, 'w')

  var client = http.get(requestOptions, function (response) {
    var status = response.statusCode
    console.log('Receiving...')

    if (status === 200) {
      response.addListener('data',   function (data) {
        fs.writeSync(outFile, data, 0, data.length, null)
        count += data.length
        if ((count - notifiedCount) > 800000) {
          console.log('Received ' + Math.floor(count / 1024) + 'K...')
          notifiedCount = count
        }
      })

      response.addListener('end',   function () {
        console.log('Received ' + Math.floor(count / 1024) + 'K total.')
        fs.closeSync(outFile)
        fs.renameSync(writePath, filePath)
        deferred.resolve(filePath)
      })

    } else {
      client.abort()
      console.error('Error requesting archive')
      deferred.reject(new Error('Error with http request: ' + util.inspect(response.headers)))
    }
  })

  return deferred.promise
}

function extractDownload(filePath) {
  var deferred = kew.defer()
  // extract to a unique directory in case multiple processes are
  // installing and extracting at once
  var extractedPath = filePath + '-extract-' + Date.now()
  var options = {cwd: extractedPath}

  mkdirp.sync(extractedPath, '0777')
  // Make double sure we have 0777 permissions; some operating systems
  // default umask does not allow write by default.
  fs.chmodSync(extractedPath, '0777')

  if (filePath.substr(-4) === '.zip') {
    console.log('Extracting zip contents')

    try {
      var zip = new AdmZip(filePath)
      zip.extractAllTo(extractedPath, true)
      deferred.resolve(extractedPath)
    } catch (err) {
      console.error('Error extracting archive')
      deferred.reject(err)
    }

  } else {
    console.log('Extracting tar contents (via spawned process)')
    cp.execFile('tar', ['jxf', filePath], options, function (err, stdout, stderr) {
      if (err) {
        console.error('Error extracting archive')
        deferred.reject(err)
      } else {
        deferred.resolve(extractedPath)
      }
    })
  }
  return deferred.promise
}

function copyIntoPlace(extractedPath, targetPath) {
  rimraf(targetPath)

  var deferred = kew.defer()
  // Look for the extracted directory, so we can rename it.
  var files = fs.readdirSync(extractedPath)
  for (var i = 0; i < files.length; i++) {
    var file = path.join(extractedPath, files[i])
    if (fs.statSync(file).isDirectory() && file.indexOf(helper.version) != -1) {
      console.log('Renaming extracted folder', file, '->', targetPath)
      ncp(file, targetPath, deferred.makeNodeResolver())
      break
    }
  }

  // Cleanup extracted directory after it's been copied
  return deferred.promise.then(function() {
    return rimraf(extractedPath)
  });
}
<|endoftext|>