<commit_before>/*
The MIT License (MIT)

Copyright (c) 2014 Shazron Abdullah

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var path = require('path'),
    fs = require('fs'),
    help = require('./help'),
    util = require('util'),
    simctl,
    bplist;

function findRuntimesGroupByDeviceProperty(list, deviceProperty, availableOnly) {
    /*
        // Example result:
        {
            "iPhone 6" : [ "iOS 8.2", "iOS 8.3"],
            "iPhone 6 Plus" : [ "iOS 8.2", "iOS 8.3"]
        }
    */

    var runtimes = {};
    var available_runtimes = {};

    list.runtimes.forEach(function(runtime) {
        if (runtime.available) {
            available_runtimes[ runtime.name ] = true;
        }
    });

    list.devices.forEach(function(deviceGroup) {
        deviceGroup.devices.forEach(function(device){
            var devicePropertyValue = device[deviceProperty];

            if (!runtimes[devicePropertyValue]) {
                runtimes[devicePropertyValue] = [];
            }
            if (availableOnly) {
                if (available_runtimes[deviceGroup.runtime]) {
                    runtimes[devicePropertyValue].push(deviceGroup.runtime);
                }
            } else {
                runtimes[devicePropertyValue].push(deviceGroup.runtime);
            }
        });
    });

    return runtimes;
}

function findAvailableRuntime(list, device_name) {

    var all_druntimes = findRuntimesGroupByDeviceProperty(list, "name", true);
    var druntime = all_druntimes[device_name];
    var runtime_found = druntime && druntime.length > 0;

    if (!runtime_found) {
        console.error(util.format('No available runtimes could be found for "%s".', device_name));
        process.exit(1);
    }

    // return most modern runtime
    return druntime.sort().pop();
}

function processDeviceTypeId(devicetypeid) {

    // the object to return
    var ret_obj = {
        name : null,
        id : null,
        runtime : null
    };

    var arr = [];
    if (devicetypeid) {
        arr = devicetypeid.split(',');
    }

    // get the devicetype from --devicetypeid
    // --devicetypeid is a string in the form "devicetype, runtime_version" (optional: runtime_version)
    if (arr.length < 1) {
      console.error('--devicetypeid was not specified.');
      process.exit(1);
    }

    var devicetype = arr[0].trim();
    if (arr.length > 1) {
        ret_obj.runtime = arr[1].trim();
    }

    // now find the devicename from the devicetype
    var options = { 'silent': true };
    var list = simctl.list(options).json;

    var devicename_found = list.devicetypes.some(function(deviceGroup) {
        if (deviceGroup.id === devicetype) {
            ret_obj.name = deviceGroup.name;
            return true;
        }

        return false;
    });

    // device name not found, exit
    if (!devicename_found) {
      console.error(util.format('Device type "%s" could not be found.', devicetype));
      process.exit(1);
    }

    // if runtime_version was not specified, we use a default. Use first available that has the device
    if (!ret_obj.runtime) {
        ret_obj.runtime = findAvailableRuntime(list, ret_obj.name);
    }

    // prepend iOS to runtime version, if necessary
    if (ret_obj.runtime.indexOf('iOS') === -1) {
        ret_obj.runtime = util.format('iOS %s', ret_obj.runtime);
    }

    // now find the deviceid (by runtime and devicename)
    var deviceid_found = list.devices.some(function(deviceGroup) {
        if (deviceGroup.runtime === ret_obj.runtime) { // found the runtime, now find the actual device matching devicename
            return deviceGroup.devices.some(function(device) {
                if (device.name === ret_obj.name) {
                    ret_obj.id = device.id;
                    return true;
                }
                return false;
            });
        }
        return false;
    });

    if (!deviceid_found) {
        console.error(util.format('Device id for device name "%s" and runtime "%s" could not be found, or is not available.', ret_obj.name, ret_obj.runtime));
        process.exit(1);
    }

    return ret_obj;
}

var command_lib = {

    init : function() {
        if (!simctl) {
            simctl = require('simctl');
        }
        var output = simctl.check_prerequisites();
        if (output.code !== 0) {
            console.error(output.output);
            process.exit(2);
        }

        if (!bplist) {
            bplist = require('bplist-parser');
        }
    },

    showsdks : function(args) {
        var options = { 'runtimes' : true };
        simctl.list(options);
    },

    showdevicetypes : function(args) {
        var options = { silent: true };
        var list = simctl.list(options).json;

        var druntimes = findRuntimesGroupByDeviceProperty(list, "name", true);
        var name_id_map = {};

        list.devicetypes.forEach(function(device) {
            name_id_map[ device.name ] = device.id;
        });

        for (var deviceName in druntimes) {
            var runtimes = druntimes[ deviceName ];
            runtimes.forEach(function(runtime){
                // remove "iOS" prefix in runtime, remove prefix "com.apple.CoreSimulator.SimDeviceType." in id
                console.log(util.format("%s, %s", name_id_map[ deviceName ].replace(/^com.apple.CoreSimulator.SimDeviceType./, ''), runtime.replace(/^iOS /, '')));
            });
        }
    },

    launch : function(args) {
        var wait_for_debugger = false,
            app_identifier,
            argv,
            app_path,
            info_plist_path;

        if (args.argv.remain.length < 2) {
            help();
            process.exit(1);
        }

        app_path = args.argv.remain[1];
        info_plist_path = path.join(app_path,'Info.plist');
        if (!fs.existsSync(info_plist_path)) {
            console.error(info_plist_path + " file not found.");
            process.exit(1);
        }

        bplist.parseFile(info_plist_path, function(err, obj) {

            if (err) {
              throw err;
            }

            app_identifier = obj[0].CFBundleIdentifier;
            argv = args.args || [];

            // get the deviceid from --devicetypeid
            // --devicetypeid is a string in the form "devicetype, runtime_version" (optional: runtime_version)
            var device = processDeviceTypeId(args.devicetypeid);

            // so now we have the deviceid, we can proceed
            simctl.extensions.start(device.id);
            simctl.install(device.id, app_path);
            simctl.launch(wait_for_debugger, device.id, app_identifier, argv);
            simctl.extensions.log(device.id, args.log);
        });
    },

    start : function(args) {
        var device = {};
        try  {
            device = processDeviceTypeId(args.devicetypeid);
        } catch (e) {
            // do nothing
        }

        simctl.extensions.start(device.id);
    }
}

module.exports = command_lib;

<commit_msg>Fixes #154 - os-sim 4.x - if devicetypeid doesn't have the prefix com.apple.CoreSimulator.SimDeviceType, add it<commit_after>/*
The MIT License (MIT)

Copyright (c) 2014 Shazron Abdullah

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var path = require('path'),
    fs = require('fs'),
    help = require('./help'),
    util = require('util'),
    simctl,
    bplist;

function findRuntimesGroupByDeviceProperty(list, deviceProperty, availableOnly) {
    /*
        // Example result:
        {
            "iPhone 6" : [ "iOS 8.2", "iOS 8.3"],
            "iPhone 6 Plus" : [ "iOS 8.2", "iOS 8.3"]
        }
    */

    var runtimes = {};
    var available_runtimes = {};

    list.runtimes.forEach(function(runtime) {
        if (runtime.available) {
            available_runtimes[ runtime.name ] = true;
        }
    });

    list.devices.forEach(function(deviceGroup) {
        deviceGroup.devices.forEach(function(device){
            var devicePropertyValue = device[deviceProperty];

            if (!runtimes[devicePropertyValue]) {
                runtimes[devicePropertyValue] = [];
            }
            if (availableOnly) {
                if (available_runtimes[deviceGroup.runtime]) {
                    runtimes[devicePropertyValue].push(deviceGroup.runtime);
                }
            } else {
                runtimes[devicePropertyValue].push(deviceGroup.runtime);
            }
        });
    });

    return runtimes;
}

function findAvailableRuntime(list, device_name) {

    var all_druntimes = findRuntimesGroupByDeviceProperty(list, "name", true);
    var druntime = all_druntimes[device_name];
    var runtime_found = druntime && druntime.length > 0;

    if (!runtime_found) {
        console.error(util.format('No available runtimes could be found for "%s".', device_name));
        process.exit(1);
    }

    // return most modern runtime
    return druntime.sort().pop();
}

function processDeviceTypeId(devicetypeid) {

    // the object to return
    var ret_obj = {
        name : null,
        id : null,
        runtime : null
    };

    var arr = [];
    if (devicetypeid) {
        arr = devicetypeid.split(',');
    }

    // get the devicetype from --devicetypeid
    // --devicetypeid is a string in the form "devicetype, runtime_version" (optional: runtime_version)
    if (arr.length < 1) {
      console.error('--devicetypeid was not specified.');
      process.exit(1);
    }

    var devicetype = arr[0].trim();
    if (arr.length > 1) {
        ret_obj.runtime = arr[1].trim();
    }

    // check whether devicetype has the "com.apple.CoreSimulator.SimDeviceType." prefix, if not, add it
    var prefix = 'com.apple.CoreSimulator.SimDeviceType.';
    if (devicetype.indexOf(prefix) != 0) {
        devicetype = prefix + devicetype;
    }

    // now find the devicename from the devicetype
    var options = { 'silent': true };
    var list = simctl.list(options).json;

    var devicename_found = list.devicetypes.some(function(deviceGroup) {
        if (deviceGroup.id === devicetype) {
            ret_obj.name = deviceGroup.name;
            return true;
        }

        return false;
    });

    // device name not found, exit
    if (!devicename_found) {
      console.error(util.format('Device type "%s" could not be found.', devicetype));
      process.exit(1);
    }

    // if runtime_version was not specified, we use a default. Use first available that has the device
    if (!ret_obj.runtime) {
        ret_obj.runtime = findAvailableRuntime(list, ret_obj.name);
    }

    // prepend iOS to runtime version, if necessary
    if (ret_obj.runtime.indexOf('iOS') === -1) {
        ret_obj.runtime = util.format('iOS %s', ret_obj.runtime);
    }

    // now find the deviceid (by runtime and devicename)
    var deviceid_found = list.devices.some(function(deviceGroup) {
        if (deviceGroup.runtime === ret_obj.runtime) { // found the runtime, now find the actual device matching devicename
            return deviceGroup.devices.some(function(device) {
                if (device.name === ret_obj.name) {
                    ret_obj.id = device.id;
                    return true;
                }
                return false;
            });
        }
        return false;
    });

    if (!deviceid_found) {
        console.error(util.format('Device id for device name "%s" and runtime "%s" could not be found, or is not available.', ret_obj.name, ret_obj.runtime));
        process.exit(1);
    }

    return ret_obj;
}

var command_lib = {

    init : function() {
        if (!simctl) {
            simctl = require('simctl');
        }
        var output = simctl.check_prerequisites();
        if (output.code !== 0) {
            console.error(output.output);
            process.exit(2);
        }

        if (!bplist) {
            bplist = require('bplist-parser');
        }
    },

    showsdks : function(args) {
        var options = { 'runtimes' : true };
        simctl.list(options);
    },

    showdevicetypes : function(args) {
        var options = { silent: true };
        var list = simctl.list(options).json;

        var druntimes = findRuntimesGroupByDeviceProperty(list, "name", true);
        var name_id_map = {};

        list.devicetypes.forEach(function(device) {
            name_id_map[ device.name ] = device.id;
        });

        for (var deviceName in druntimes) {
            var runtimes = druntimes[ deviceName ];
            runtimes.forEach(function(runtime){
                // remove "iOS" prefix in runtime, remove prefix "com.apple.CoreSimulator.SimDeviceType." in id
                console.log(util.format("%s, %s", name_id_map[ deviceName ].replace(/^com.apple.CoreSimulator.SimDeviceType./, ''), runtime.replace(/^iOS /, '')));
            });
        }
    },

    launch : function(args) {
        var wait_for_debugger = false,
            app_identifier,
            argv,
            app_path,
            info_plist_path;

        if (args.argv.remain.length < 2) {
            help();
            process.exit(1);
        }

        app_path = args.argv.remain[1];
        info_plist_path = path.join(app_path,'Info.plist');
        if (!fs.existsSync(info_plist_path)) {
            console.error(info_plist_path + " file not found.");
            process.exit(1);
        }

        bplist.parseFile(info_plist_path, function(err, obj) {

            if (err) {
              throw err;
            }

            app_identifier = obj[0].CFBundleIdentifier;
            argv = args.args || [];

            // get the deviceid from --devicetypeid
            // --devicetypeid is a string in the form "devicetype, runtime_version" (optional: runtime_version)
            var device = processDeviceTypeId(args.devicetypeid);

            // so now we have the deviceid, we can proceed
            simctl.extensions.start(device.id);
            simctl.install(device.id, app_path);
            simctl.launch(wait_for_debugger, device.id, app_identifier, argv);
            simctl.extensions.log(device.id, args.log);
        });
    },

    start : function(args) {
        var device = {};
        try  {
            device = processDeviceTypeId(args.devicetypeid);
        } catch (e) {
            // do nothing
        }

        simctl.extensions.start(device.id);
    }
}

module.exports = command_lib;

<|endoftext|>


<commit_before>var SteamUser = require('../index.js');
var Steam = require('steam');
var SteamID = require('steamid');
var ByteBuffer = require('bytebuffer');
var Helpers = require('./helpers.js');

SteamUser.prototype.chatMessage = SteamUser.prototype.chatMsg = function(recipient, message, type) {
	recipient = Helpers.steamID(recipient);

	type = type || Steam.EChatEntryType.ChatMsg;

	if([SteamID.Type.CLAN, SteamID.Type.CHAT].indexOf(recipient.type) != -1) {
		// It's a chat message
		var msg = new ByteBuffer(8 + 8 + 4 + Buffer.byteLength(message) + 1, ByteBuffer.LITTLE_ENDIAN);
		msg.writeUint64(this.steamID.getSteamID64()); // steamIdChatter
		msg.writeUint64(toChatID(recipient).getSteamID64()); // steamIdChatRoom
		msg.writeUint32(type); // chatMsgType
		msg.writeCString(message);
		this._send(Steam.EMsg.ClientChatMsg, msg.flip());
	} else {
		// It's a friend message
		var payload = new ByteBuffer(Buffer.byteLength(message) + 1, ByteBuffer.LITTLE_ENDIAN);
		payload.writeCString(message);

		this._send(Steam.EMsg.ClientFriendMsg, {
			"steamid": recipient.getSteamID64(),
			"message": payload.flip(),
			"chat_entry_type": type
		});
	}
};

SteamUser.prototype.chatTyping = function(recipient) {
	this.chatMessage(recipient, "", Steam.EChatEntryType.Typing);
};

SteamUser.prototype.joinChat = function(steamID) {
	var msg = new ByteBuffer(8, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	this._send(Steam.EMsg.ClientJoinChat, msg.flip());
};

SteamUser.prototype.leaveChat = function(steamID) {
	var msg = new ByteBuffer(32, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint32(Steam.EChatInfoType.StateChange); // type
	msg.writeUint64(this.steamID.getSteamID64());
	msg.writeUint32(Steam.EChatMemberStateChange.Left);
	msg.writeUint64(this.steamID.getSteamID64());
	this._send(Steam.EMsg.ClientChatMemberInfo, msg.flip());

	steamID = Helpers.steamID(steamID);

	delete this.chats[steamID.getSteamID64()];
};

SteamUser.prototype.setChatPrivate = function(steamID) {
	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.LockChat);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.setChatPublic = function(steamID) {
	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.UnlockChat);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.setChatOfficersOnly = function(steamID, moderated) {
	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.SetModerated);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.unsetChatOfficersOnly = function(steamID, moderated) {
	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.SetUnmoderated);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.kickFromChat = function(chatID, userID) {
	userID = Helpers.steamID(userID);

	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(chatID).getSteamID64()); // steamIdChat
	msg.writeUint64(userID.getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.Kick);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.banFromChat = function(chatID, userID) {
	userID = Helpers.steamID(userID);

	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(chatID).getSteamID64()); // steamIdChat
	msg.writeUint64(userID.getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.Ban);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.unbanFromChat = function(chatID, userID) {
	userID = Helpers.steamID(userID);

	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(chatID).getSteamID64()); // steamIdChat
	msg.writeUint64(userID.getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.UnBan);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.inviteToChat = function(chatID, userID) {
	userID = Helpers.steamID(userID);

	this._send(Steam.EMsg.ClientChatInvite, {
		"steam_id_invited": userID.getSteamID64(),
		"steam_id_chat": toChatID(chatID).getSteamID64()
	});
};

// TODO: Ability to create chats

// Handlers

SteamUser.prototype._handlers[Steam.EMsg.ClientFriendMsgIncoming] = function(body) {
	var senderID = new SteamID(body.steamid_from.toString());
	var message = body.message.readCString();

	switch(body.chat_entry_type) {
		case Steam.EChatEntryType.ChatMsg:
			this.emit('friendMessage', senderID, message);
			this.emit('friendMessage#' + senderID.getSteamID64(), senderID, message);
			break;

		case Steam.EChatEntryType.Typing:
			this.emit('friendTyping', senderID);
			this.emit('friendTyping#' + senderID.getSteamID64(), senderID);
			break;
	}
};

SteamUser.prototype._handlers[Steam.EMsg.ClientFriendMsgEchoToSender] = function(body) {
	var recipientID = new SteamID(body.steamid_from.toString());
	var message = body.message.readCString();

	switch(body.chat_entry_type) {
		case Steam.EChatEntryType.ChatMsg:
			this.emit('friendMessageEcho', recipientID, message);
			this.emit('friendMessageEcho#' + recipientID.getSteamID64(), recipientID, message);
			break;

		case Steam.EChatEntryType.Typing:
			this.emit('friendTypingEcho', recipientID);
			this.emit('friendTypingEcho#' + recipientID.getSteamID64(), recipientID);
			break;
	}
};

SteamUser.prototype._handlers[Steam.EMsg.ClientChatMsg] = function(body) {
	var chatter = new SteamID(body.readUint64().toString());
	var room = fromChatID(body.readUint64());
	var entryType = body.readUint32();
	var message = body.readCString();

	if(entryType != Steam.EChatEntryType.ChatMsg) {
		// Not sure what kind of chat entry types are possible for chat rooms...
		return;
	}

	this.emit('chatMessage', room, chatter, message);
	this.emit('chatMessage#' + room.getSteamID64(), room, chatter, message);
	this.emit('chatMessage#' + room.getSteamID64() + '#' + chatter.getSteamID64(), room, chatter, message);
};

SteamUser.prototype._handlers[Steam.EMsg.ClientChatInvite] = function(body) {
	this.emit('chatInvite', fromChatID(body.steam_id_chat), body.chat_name, new SteamID(body.steam_id_patron.toString()));
};

SteamUser.prototype._handlers[Steam.EMsg.ClientChatMemberInfo] = function(body) {
	var chatID = fromChatID(body.readUint64().toString());
	var infoType = body.readUint32();

	switch(infoType) {
		case Steam.EChatInfoType.StateChange:
			// A user's state changed
			var target = new SteamID(body.readUint64().toString());
			var action = body.readUint32();
			var actor = new SteamID(body.readUint64().toString());

			if(action & Steam.EChatMemberStateChange.Entered) {
				this._emitIdEvent('chatUserJoined', chatID, target);;
			}

			if(action & Steam.EChatMemberStateChange.Left) {
				this._emitIdEvent('chatUserLeft', chatID, target);
			}

			if(action & Steam.EChatMemberStateChange.Disconnected) {
				this._emitIdEvent('chatUserDisconnected', chatID, target);
			}

			if(action & Steam.EChatMemberStateChange.Kicked) {
				this._emitIdEvent('chatUserKicked', chatID, target, actor);
			}

			if(action & Steam.EChatMemberStateChange.Banned) {
				this._emitIdEvent('chatUserBanned', chatID, target, actor);
			}

			if(action & Steam.EChatMemberStateChange.VoiceSpeaking) {
				this._emitIdEvent('chatUserSpeaking', chatID, target);
			}

			if(action & Steam.EChatMemberStateChange.VoiceDoneSpeaking) {
				this._emitIdEvent('chatUserDoneSpeaking', chatID, target);
			}

			break;
	}
};

// Private functions

/**
 * If steamID is a clan ID, converts to the appropriate chat ID. Otherwise, returns it untouched.
 * @param SteamID steamID
 * @returns SteamID
 */
function toChatID(steamID) {
	steamID = Helpers.steamID(steamID);

	if(steamID.type == SteamID.Type.CLAN) {
		steamID.type = SteamID.Type.CHAT;
		steamID.instance |= SteamID.ChatInstanceFlags.Clan;
	}

	return steamID;
}

/**
 * If steamID is a clan chat ID, converts to the appropriate clan ID. Otherwise, returns it untouched.
 * @param SteamID steamID
 * @returns SteamID
 */
function fromChatID(steamID) {
	steamID = Helpers.steamID(steamID);

	if(steamID.type == SteamID.Type.CHAT && steamID.instance & SteamID.ChatInstanceFlags.Clan) {
		steamID.type = SteamID.Type.CLAN;
		steamID.instance &= ~SteamID.ChatInstanceFlags.Clan;
	}

	return steamID;
}
<commit_msg>Store chat room data and emit events for user actions<commit_after>var SteamUser = require('../index.js');
var Steam = require('steam');
var SteamID = require('steamid');
var ByteBuffer = require('bytebuffer');
var BinaryKVParser = require('binarykvparser');
var Helpers = require('./helpers.js');

SteamUser.prototype.chatMessage = SteamUser.prototype.chatMsg = function(recipient, message, type) {
	recipient = Helpers.steamID(recipient);

	type = type || Steam.EChatEntryType.ChatMsg;

	if([SteamID.Type.CLAN, SteamID.Type.CHAT].indexOf(recipient.type) != -1) {
		// It's a chat message
		var msg = new ByteBuffer(8 + 8 + 4 + Buffer.byteLength(message) + 1, ByteBuffer.LITTLE_ENDIAN);
		msg.writeUint64(this.steamID.getSteamID64()); // steamIdChatter
		msg.writeUint64(toChatID(recipient).getSteamID64()); // steamIdChatRoom
		msg.writeUint32(type); // chatMsgType
		msg.writeCString(message);
		this._send(Steam.EMsg.ClientChatMsg, msg.flip());
	} else {
		// It's a friend message
		var payload = new ByteBuffer(Buffer.byteLength(message) + 1, ByteBuffer.LITTLE_ENDIAN);
		payload.writeCString(message);

		this._send(Steam.EMsg.ClientFriendMsg, {
			"steamid": recipient.getSteamID64(),
			"message": payload.flip(),
			"chat_entry_type": type
		});
	}
};

SteamUser.prototype.chatTyping = function(recipient) {
	this.chatMessage(recipient, "", Steam.EChatEntryType.Typing);
};

SteamUser.prototype.joinChat = function(steamID, callback) {
	var msg = new ByteBuffer(8, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	this._send(Steam.EMsg.ClientJoinChat, msg.flip());

	if(callback) {
		this.once('chatEnter#' + Helpers.steamID(steamID).getSteamID64(), function(chatID, result) {
			callback(result);
		});
	}
};

SteamUser.prototype.leaveChat = function(steamID) {
	var msg = new ByteBuffer(32, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint32(Steam.EChatInfoType.StateChange); // type
	msg.writeUint64(this.steamID.getSteamID64());
	msg.writeUint32(Steam.EChatMemberStateChange.Left);
	msg.writeUint64(this.steamID.getSteamID64());
	this._send(Steam.EMsg.ClientChatMemberInfo, msg.flip());

	steamID = Helpers.steamID(steamID);

	delete this.chats[steamID.getSteamID64()];
};

SteamUser.prototype.setChatPrivate = function(steamID) {
	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.LockChat);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.setChatPublic = function(steamID) {
	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.UnlockChat);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.setChatOfficersOnly = function(steamID, moderated) {
	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.SetModerated);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.unsetChatOfficersOnly = function(steamID, moderated) {
	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdChat
	msg.writeUint64(toChatID(steamID).getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.SetUnmoderated);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.kickFromChat = function(chatID, userID) {
	userID = Helpers.steamID(userID);

	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(chatID).getSteamID64()); // steamIdChat
	msg.writeUint64(userID.getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.Kick);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.banFromChat = function(chatID, userID) {
	userID = Helpers.steamID(userID);

	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(chatID).getSteamID64()); // steamIdChat
	msg.writeUint64(userID.getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.Ban);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.unbanFromChat = function(chatID, userID) {
	userID = Helpers.steamID(userID);

	var msg = new ByteBuffer(20, ByteBuffer.LITTLE_ENDIAN);
	msg.writeUint64(toChatID(chatID).getSteamID64()); // steamIdChat
	msg.writeUint64(userID.getSteamID64()); // steamIdUserToActOn
	msg.writeUint32(Steam.EChatAction.UnBan);
	this._send(Steam.EMsg.ClientChatAction, msg.flip());
};

SteamUser.prototype.inviteToChat = function(chatID, userID) {
	userID = Helpers.steamID(userID);

	this._send(Steam.EMsg.ClientChatInvite, {
		"steam_id_invited": userID.getSteamID64(),
		"steam_id_chat": toChatID(chatID).getSteamID64()
	});
};

// TODO: Ability to create chats

// Handlers

SteamUser.prototype._handlers[Steam.EMsg.ClientFriendMsgIncoming] = function(body) {
	var senderID = new SteamID(body.steamid_from.toString());
	var message = body.message.readCString();

	switch(body.chat_entry_type) {
		case Steam.EChatEntryType.ChatMsg:
			this.emit('friendMessage', senderID, message);
			this.emit('friendMessage#' + senderID.getSteamID64(), senderID, message);
			break;

		case Steam.EChatEntryType.Typing:
			this.emit('friendTyping', senderID);
			this.emit('friendTyping#' + senderID.getSteamID64(), senderID);
			break;
	}
};

SteamUser.prototype._handlers[Steam.EMsg.ClientFriendMsgEchoToSender] = function(body) {
	var recipientID = new SteamID(body.steamid_from.toString());
	var message = body.message.readCString();

	switch(body.chat_entry_type) {
		case Steam.EChatEntryType.ChatMsg:
			this.emit('friendMessageEcho', recipientID, message);
			this.emit('friendMessageEcho#' + recipientID.getSteamID64(), recipientID, message);
			break;

		case Steam.EChatEntryType.Typing:
			this.emit('friendTypingEcho', recipientID);
			this.emit('friendTypingEcho#' + recipientID.getSteamID64(), recipientID);
			break;
	}
};

SteamUser.prototype._handlers[Steam.EMsg.ClientChatMsg] = function(body) {
	var chatter = new SteamID(body.readUint64().toString());
	var room = fromChatID(body.readUint64());
	var entryType = body.readUint32();
	var message = body.readCString();

	if(entryType != Steam.EChatEntryType.ChatMsg) {
		// Not sure what kind of chat entry types are possible for chat rooms...
		return;
	}

	this.emit('chatMessage', room, chatter, message);
	this.emit('chatMessage#' + room.getSteamID64(), room, chatter, message);
	this.emit('chatMessage#' + room.getSteamID64() + '#' + chatter.getSteamID64(), room, chatter, message);
};

SteamUser.prototype._handlers[Steam.EMsg.ClientChatInvite] = function(body) {
	this.emit('chatInvite', fromChatID(body.steam_id_chat), body.chat_name, new SteamID(body.steam_id_patron.toString()));
};

SteamUser.prototype._handlers[Steam.EMsg.ClientChatEnter] = function(body) {
	var chatID = fromChatID(body.readUint64());
	body.skip(28);
	var chatFlags = body.readUint8();
	var response = body.readUint32();
	var numMembers = body.readUint32();

	if(response == Steam.EChatRoomEnterResponse.Success) {
		var chatName = body.readCString();

		var sid64 = chatID.getSteamID64();
		this.chats[sid64] = {
			"name": chatName,
			"private": !!(chatFlags & Steam.EChatFlags.Locked),
			"invisibleToFriends": !!(chatFlags & Steam.EChatFlags.InvisibleToFriends),
			"officersOnlyChat": !!(chatFlags & Steam.EChatFlags.Moderated),
			"unjoinable": !!(chatFlags & Steam.EChatFlags.Unjoinable),
			"members": {}
		};

		var member;

		for(var i = 0; i < numMembers; i++) {
			member = BinaryKVParser.parse(body).MessageObject;
			this.chats[sid64].members[member.steamid.toString()] = {
				"rank": member.Details,
				"permissions": member.permissions
			}
		}
	}

	this._emitIdEvent('chatEnter', chatID, response);
};

SteamUser.prototype._handlers[Steam.EMsg.ClientChatMemberInfo] = function(body) {
	var chatID = fromChatID(body.readUint64().toString());
	var infoType = body.readUint32();

	var target = null;
	var action = null;
	var actor = null;

	if(infoType == Steam.EChatInfoType.StateChange) {
		// A user's state changed
		target = new SteamID(body.readUint64().toString());
		action = body.readUint32();
		actor = new SteamID(body.readUint64().toString());
	}

	var sid64 = chatID.getSteamID64();

	if(!this.chats[sid64]) {
		// We're not in this chat
		return;
	}

	if(infoType == Steam.EChatInfoType.InfoUpdate || (action !== null && action & Steam.EChatMemberStateChange.Entered)) {
		// There's a user info here
		var userInfo = BinaryKVParser.parse(body).MessageObject;
		this.chats[sid64].members[userInfo.steamid.toString()] = {
			"rank": userInfo.Details,
			"permissions": userInfo.permissions
		};
	}

	if(target !== null && action !== null) {
		// Someone was affected, so emit the proper event
		var target64 = target.getSteamID64();

		if(action & Steam.EChatMemberStateChange.Entered) {
			this._emitIdEvent('chatUserJoined', chatID, target);;
		}

		if(action & Steam.EChatMemberStateChange.Left) {
			this._emitIdEvent('chatUserLeft', chatID, target);
			delete this.chats[sid64].members[target64];
		}

		if(action & Steam.EChatMemberStateChange.Disconnected) {
			this._emitIdEvent('chatUserDisconnected', chatID, target);
			delete this.chats[sid64].members[target64];
		}

		if(action & Steam.EChatMemberStateChange.Kicked) {
			this._emitIdEvent('chatUserKicked', chatID, target, actor);
			delete this.chats[sid64].members[target64];
		}

		if(action & Steam.EChatMemberStateChange.Banned) {
			this._emitIdEvent('chatUserBanned', chatID, target, actor);
			delete this.chats[sid64].members[target64];
		}

		if(action & Steam.EChatMemberStateChange.VoiceSpeaking) {
			this._emitIdEvent('chatUserSpeaking', chatID, target);
		}

		if(action & Steam.EChatMemberStateChange.VoiceDoneSpeaking) {
			this._emitIdEvent('chatUserDoneSpeaking', chatID, target);
		}
	}

	if(target !== null && target.getSteamID64() == this.steamID.getSteamID64() && action !== null && action < Steam.EChatMemberStateChange.VoiceSpeaking) {
		// We've left this room, delete it
		this._emitIdEvent('chatLeft', chatID);
		delete this.chats[sid64];
	}
};

// Private functions

/**
 * If steamID is a clan ID, converts to the appropriate chat ID. Otherwise, returns it untouched.
 * @param SteamID steamID
 * @returns SteamID
 */
function toChatID(steamID) {
	steamID = Helpers.steamID(steamID);

	if(steamID.type == SteamID.Type.CLAN) {
		steamID.type = SteamID.Type.CHAT;
		steamID.instance |= SteamID.ChatInstanceFlags.Clan;
	}

	return steamID;
}

/**
 * If steamID is a clan chat ID, converts to the appropriate clan ID. Otherwise, returns it untouched.
 * @param SteamID steamID
 * @returns SteamID
 */
function fromChatID(steamID) {
	steamID = Helpers.steamID(steamID);

	if(steamID.type == SteamID.Type.CHAT && steamID.instance & SteamID.ChatInstanceFlags.Clan) {
		steamID.type = SteamID.Type.CLAN;
		steamID.instance &= ~SteamID.ChatInstanceFlags.Clan;
	}

	return steamID;
}
<|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
      '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
      '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
      '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
      ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
    // Task configuration.
    clean: {
      build: ["dest/<%= pkg.name %>/"],
      public_html: ['<%= pkg.public_html %>'],
    },
    concat: {
      options: {
        banner: '<%= banner %>',
        stripBanners: true
      },
      dist: {
        src: ['lib/<%= pkg.name %>.js', 'src/*.js'],
        dest: 'dest/<%= pkg.name %>.js'
      },
    },
    uglify: {
      options: {
        banner: '<%= banner %>'
      },
      dist: {
        src: '<%= concat.dist.dest %>',
        dest: 'dest/<%= pkg.name %>.min.js'
      },
    },
    copy: {
      public_html: {
        files: [
          {expand: true, cwd: 'public_html/', src: ['**'], dest: '<%= pkg.public_html %>'},
          {expand: true, cwd: 'dest/', src: ['**'], dest: '<%= pkg.public_html %>/dest'}
        ]
      }
    },
    nodeunit: {
      files: ['test/**/*_test.js']
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib: {
        options: {
          jshintrc: 'lib/.jshintrc'
        },
        src: ['lib/**/*.js']
      },
      test: {
        src: ['test/**/*.js']
      },
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      lib: {
        files: '<%= jshint.lib.src %>',
        tasks: ['jshint:lib', 'nodeunit']
      },
      test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'nodeunit']
      },
    },
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-nodeunit');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-copy');

  // Default task.
  grunt.registerTask('default', ['jshint', 'nodeunit', 'clean:build', 'concat', 'uglify']);

  // Custom tasks.
  grunt.registerTask('public_html', ['clean:public_html', 'copy:public_html']);
  grunt.registerTask('public_build', ['default', 'public_html']);

};
<commit_msg>use strictの位置を移動<commit_after>module.exports = function(grunt) {
'use strict';

  // Project configuration.
  grunt.initConfig({
    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
      '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
      '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
      '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
      ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
    // Task configuration.
    clean: {
      build: ["dest/<%= pkg.name %>/"],
      public_html: ['<%= pkg.public_html %>'],
    },
    concat: {
      options: {
        banner: '<%= banner %>',
        stripBanners: true
      },
      dist: {
        src: ['lib/<%= pkg.name %>.js', 'src/*.js'],
        dest: 'dest/<%= pkg.name %>.js'
      },
    },
    uglify: {
      options: {
        banner: '<%= banner %>'
      },
      dist: {
        src: '<%= concat.dist.dest %>',
        dest: 'dest/<%= pkg.name %>.min.js'
      },
    },
    copy: {
      public_html: {
        files: [
          {expand: true, cwd: 'public_html/', src: ['**'], dest: '<%= pkg.public_html %>'},
          {expand: true, cwd: 'dest/', src: ['**'], dest: '<%= pkg.public_html %>/dest'}
        ]
      }
    },
    nodeunit: {
      files: ['test/**/*_test.js']
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib: {
        options: {
          jshintrc: 'lib/.jshintrc'
        },
        src: ['lib/**/*.js']
      },
      test: {
        src: ['test/**/*.js']
      },
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      lib: {
        files: '<%= jshint.lib.src %>',
        tasks: ['jshint:lib', 'nodeunit']
      },
      test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'nodeunit']
      },
    },
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-nodeunit');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-copy');

  // Default task.
  grunt.registerTask('default', ['jshint', 'nodeunit', 'clean:build', 'concat', 'uglify']);

  // Custom tasks.
  grunt.registerTask('public_html', ['clean:public_html', 'copy:public_html']);
  grunt.registerTask('public_build', ['default', 'public_html']);

};
<|endoftext|>


<commit_before>import {inject, bindable} from 'aurelia-framework';
import TranslationService from 'resources/services/translation-service';
import {Router} from 'aurelia-router';
import LocationService from 'resources/services/location-service';
import FiltersService from 'resources/services/filters-service';
import LogService from 'resources/services/log-service';
import {EventAggregator} from 'aurelia-event-aggregator';

@inject(Router, TranslationService, LocationService,
FiltersService, LogService, EventAggregator)
export class Map {
    @bindable remarks = [];
    @bindable radiusChanged = null;
    @bindable center = null;

  constructor(router, translationService, location,
  filtersService, logService, eventAggregator) {
    this.router = router;
    this.translationService = translationService;
    this.location = location;
    this.filtersService = filtersService;
    this.log = logService;
    this.eventAggregator = eventAggregator;
    this.map = null;
    this.radius = null;
    this.defaultRemarkMarkerColor = '9F6807';
    this.userMarker = null;
    this.centerInitialized = false;
    this.mapDragged = false;
  }

  async attached() {
    let filters = this.filtersService.filters;
    let latitude = filters.center.latitude;
    let longitude = filters.center.longitude;
    let userLatitude = this.location.current.latitude;
    let userLongitude = this.location.current.longitude;
    if (latitude === 0 && longitude === 0) {
      latitude = userLatitude;
      longitude = userLongitude;
    }
    this.userPosition = new google.maps.LatLng(userLatitude, userLongitude);
    this.position = new google.maps.LatLng(latitude, longitude);
    this.drawMap();
    this.drawUserMarker();
    // this.drawRadius();
    this.eventAggregator.publish('map:loaded');
    this.locationLoadedSubscription = await this.eventAggregator.subscribe('location:loaded',
        async response => await this.locationUpdated(response));
    this.resetCenterSubscription = await this.eventAggregator.subscribe('location:reset-center',
      async response => {
        this.mapDragged = false;
        this.position = new google.maps.LatLng(response.latitude, response.longitude);
        await this.map.setCenter(this.position);
      });
  }

  detached() {
    this.locationLoadedSubscription.dispose();
    this.resetCenterSubscription.dispose();
  }

  remarksChanged(newValue) {
    newValue.forEach(remark => this.drawRemarkMarker(remark));
  }

  centerChanged(newValue) {
    try {
      if (this.centerInitialized) {
        return;
      }
      if (this.map === null) {
        return;
      }
      if (typeof newValue === 'undefined' || newValue === null) {
        return;
      }
      this.position = new google.maps.LatLng(newValue.latitude, newValue.longitude);
      this.map.setCenter(this.position);
      this.centerInitialized = true;
    } catch (error) {
      this.log.error('map_center_changed', error);
    }
  }

  async locationUpdated(location) {
    try {
      let lng = location.coords.longitude;
      let lat = location.coords.latitude;
      this.userPosition = { lat, lng };
      this.drawUserMarker();
      this.filtersService.setDefaultCenter({latitude: lat, longitude: lng});
    } catch (error) {
      this.log.error('map_location_updated', error);
    }
  }

  drawMap() {
    try {
      let filters = this.filtersService.filters;
      this.log.trace('map_draw', {position: this.position, filters: filters});
      this.map = new google.maps.Map(document.getElementById('map'), {
        zoom: filters.map.zoomLevel,
        minZoom: 8,
        center: this.position
      });
    } catch (error) {
      this.log.error('map_draw_map', error);
    }

    this.map.addListener('zoom_changed', () => {
      this.filtersService.setZoomLevel(this.map.getZoom());
      this._recalculateRadius();
    });
    this.map.addListener('dragend', () => {
      this._recalculateRadius();
    });

    this.map.addListener('center_changed', () => {
      try {
        let center = this.map.getCenter();
        let latitude = center.lat();
        let longitude = center.lng();
        this.filtersService.setCenter({latitude: latitude, longitude: longitude});
      } catch (error) {
        this.log.error('map_center_changed_listener', error);
      }
    });
  }

  _recalculateRadius() {
    try {
      this.mapDragged = true;
      let bounds = this.map.getBounds();
      let center = bounds.getCenter();
      let northEast = bounds.getNorthEast();
      let earthRadius = 6378000.41;
      let centerLat = center.lat() / 57.2958;
      let centerLng = center.lng() / 57.2958;
      let northEastLat = northEast.lat() / 57.2958;
      let northEastLng = northEast.lng() / 57.2958;
      let radiusMeters = earthRadius * Math.acos(Math.sin(centerLat) * Math.sin(northEastLat) +
                          Math.cos(centerLat) * Math.cos(northEastLat) * Math.cos(northEastLng - centerLng));

      this.filtersService.setRadius(radiusMeters);
      if (this.radiusChanged !== null) {
        let args = {
          radiusMeters,
          center
        };
        this.radiusChanged(args);
      }
    } catch (error) {
      this.log.error('map_recalculate_radius', error);
    }
  }

  drawUserMarker() {
    try {
      let lat = this.userPosition.lat;
      let lng = this.userPosition.lng;
      if (this.userMarker !== null) {
        this.userMarker.setMap(null);
      }
      let title = this.translationService.tr('common.user');
      let content = this.translationService.tr('common.you_are_here');
      this.userMarker = this.drawMarker(lng, lat, title, content, 'FFEBEE');
      if (!this.mapDragged) {
        this.moveMarker(this.userMarker, lat, lng);
      }
    } catch (error) {
      this.log.error('map_draw_user_marker', error);
    }
  }

  moveMarker(marker, lat, lng) {
    try {
      let position = new google.maps.LatLng(lat, lng);
      marker.setPosition(position);
      // this.map.panTo(position);
    } catch (error) {
      this.log.error('map_move_marker', error);
    }
  }

  drawRemarkMarker(remark) {
    try {
      let longitude = remark.location.coordinates[0];
      let latitude = remark.location.coordinates[1];
      let category = this.translationService.tr(`remark.category_${remark.category}`);
      let color = this.getRemarMarkerkColor(remark);
      let detailsText = this.translationService.tr('common.details');
      let url = this.router.generate('remark', {id: remark.id});
      let description = remark.description ? remark.description : '';
      let enlarge = remark.rating >= 2 && this.filters.distinguishLiked;
      description = description.length > 15 ? `${description.substring(0, 15)}...` : description;
      let content = `<strong>${category}</strong><br/><a href="${url}" class="btn waves-effect waves-light">${detailsText}</a><br/>${description}`;
      this.drawMarker(longitude, latitude, detailsText, content, color, enlarge);
    } catch (error) {
      this.log.error('map_draw_remark_marker', error);
    }
  }

  getRemarMarkerkColor(remark) {
    if (remark.selected) {
      return '3399FF';
    }
    if (remark.resolved) {
      return '009720';
    }

    switch (remark.category) {
    case 'accidents': return 'FBF514';
    case 'damages': return 'E40521';
    case 'litter': return '9F6807';
    default: return this.defaultRemarkMarkerColor;
    }
  }

  drawMarker(longitude, latitude, title, content, color, enlarge) {
    try {
      let position = new google.maps.LatLng(latitude, longitude);
      let infowindow = new google.maps.InfoWindow({
        content: content
      });
      let size = enlarge ? new google.maps.Size(30, 45) : new google.maps.Size(20, 30);
      let marker = new google.maps.Marker({
        position: position,
        title: title,
        map: this.map,
        icon: {
          url: `https://chart.googleapis.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|${color}`,
          scaledSize: size
        }
      });
      marker.addListener('click', function() {
        infowindow.open(map, marker);
      });

      return marker;
    } catch (error) {
      this.log.error('map_draw_marker', error);
    }
  }

  drawRadius() {
    this.radius = new google.maps.Circle({
      strokeColor: '#308AF1',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#308AF1',
      fillOpacity: 0.3,
      map: this.map,
      center: this.position,
      radius: parseFloat(this.filters.radius)
    });
  }
}
<commit_msg>try-catch blocks removed from map.js<commit_after>import {inject, bindable} from 'aurelia-framework';
import TranslationService from 'resources/services/translation-service';
import {Router} from 'aurelia-router';
import LocationService from 'resources/services/location-service';
import FiltersService from 'resources/services/filters-service';
import LogService from 'resources/services/log-service';
import {EventAggregator} from 'aurelia-event-aggregator';

@inject(Router, TranslationService, LocationService,
FiltersService, LogService, EventAggregator)
export class Map {
    @bindable remarks = [];
    @bindable radiusChanged = null;
    @bindable center = null;

  constructor(router, translationService, location,
  filtersService, logService, eventAggregator) {
    this.router = router;
    this.translationService = translationService;
    this.location = location;
    this.filtersService = filtersService;
    this.log = logService;
    this.eventAggregator = eventAggregator;
    this.map = null;
    this.radius = null;
    this.defaultRemarkMarkerColor = '9F6807';
    this.userMarker = null;
    this.centerInitialized = false;
    this.mapDragged = false;
  }

  async attached() {
    let filters = this.filtersService.filters;
    let latitude = filters.center.latitude;
    let longitude = filters.center.longitude;
    let userLatitude = this.location.current.latitude;
    let userLongitude = this.location.current.longitude;
    if (latitude === 0 && longitude === 0) {
      latitude = userLatitude;
      longitude = userLongitude;
    }
    this.userPosition = new google.maps.LatLng(userLatitude, userLongitude);
    this.position = new google.maps.LatLng(latitude, longitude);
    this.drawMap();
    this.drawUserMarker();
    // this.drawRadius();
    this.eventAggregator.publish('map:loaded');
    this.locationLoadedSubscription = await this.eventAggregator.subscribe('location:loaded',
        async response => await this.locationUpdated(response));
    this.resetCenterSubscription = await this.eventAggregator.subscribe('location:reset-center',
      async response => {
        this.mapDragged = false;
        this.position = new google.maps.LatLng(response.latitude, response.longitude);
        await this.map.setCenter(this.position);
      });
  }

  detached() {
    this.locationLoadedSubscription.dispose();
    this.resetCenterSubscription.dispose();
  }

  remarksChanged(newValue) {
    newValue.forEach(remark => this.drawRemarkMarker(remark));
  }

  centerChanged(newValue) {
    if (this.centerInitialized) {
      return;
    }
    if (this.map === null) {
      return;
    }
    if (typeof newValue === 'undefined' || newValue === null) {
      return;
    }
    this.position = new google.maps.LatLng(newValue.latitude, newValue.longitude);
    this.map.setCenter(this.position);
    this.centerInitialized = true;
  }

  async locationUpdated(location) {
    let lng = location.coords.longitude;
    let lat = location.coords.latitude;
    this.userPosition = { lat, lng };
    this.drawUserMarker();
    this.filtersService.setDefaultCenter({latitude: lat, longitude: lng});
  }

  drawMap() {
    let filters = this.filtersService.filters;
    this.log.trace('map_draw', {position: this.position, filters: filters});
    this.map = new google.maps.Map(document.getElementById('map'), {
      zoom: filters.map.zoomLevel,
      minZoom: 8,
      center: this.position
    });

    this.map.addListener('zoom_changed', () => {
      this.filtersService.setZoomLevel(this.map.getZoom());
      this._recalculateRadius();
    });
    this.map.addListener('dragend', () => {
      this._recalculateRadius();
    });

    this.map.addListener('center_changed', () => {
      let center = this.map.getCenter();
      let latitude = center.lat();
      let longitude = center.lng();
      this.filtersService.setCenter({latitude: latitude, longitude: longitude});
    });
  }

  _recalculateRadius() {
    this.mapDragged = true;
    let bounds = this.map.getBounds();
    let center = bounds.getCenter();
    let northEast = bounds.getNorthEast();
    let earthRadius = 6378000.41;
    let centerLat = center.lat() / 57.2958;
    let centerLng = center.lng() / 57.2958;
    let northEastLat = northEast.lat() / 57.2958;
    let northEastLng = northEast.lng() / 57.2958;
    let radiusMeters = earthRadius * Math.acos(Math.sin(centerLat) * Math.sin(northEastLat) +
                        Math.cos(centerLat) * Math.cos(northEastLat) * Math.cos(northEastLng - centerLng));

    this.filtersService.setRadius(radiusMeters);
    if (this.radiusChanged !== null) {
      let args = {
        radiusMeters,
        center
      };
      this.radiusChanged(args);
    }
  }

  drawUserMarker() {
    let lat = this.userPosition.lat;
    let lng = this.userPosition.lng;
    if (this.userMarker !== null) {
      this.userMarker.setMap(null);
    }
    let title = this.translationService.tr('common.user');
    let content = this.translationService.tr('common.you_are_here');
    this.userMarker = this.drawMarker(lng, lat, title, content, 'FFEBEE');
    if (!this.mapDragged) {
      this.moveMarker(this.userMarker, lat, lng);
    }
  }

  moveMarker(marker, lat, lng) {
    let position = new google.maps.LatLng(lat, lng);
    marker.setPosition(position);
    // this.map.panTo(position);
  }

  drawRemarkMarker(remark) {
    let longitude = remark.location.coordinates[0];
    let latitude = remark.location.coordinates[1];
    let category = this.translationService.tr(`remark.category_${remark.category}`);
    let color = this.getRemarMarkerkColor(remark);
    let detailsText = this.translationService.tr('common.details');
    let url = this.router.generate('remark', {id: remark.id});
    let description = remark.description ? remark.description : '';
    let enlarge = remark.rating >= 2 && this.filters.distinguishLiked;
    description = description.length > 15 ? `${description.substring(0, 15)}...` : description;
    let content = `<strong>${category}</strong><br/><a href="${url}" class="btn waves-effect waves-light">${detailsText}</a><br/>${description}`;
    this.drawMarker(longitude, latitude, detailsText, content, color, enlarge);
  }

  getRemarMarkerkColor(remark) {
    if (remark.selected) {
      return '3399FF';
    }
    if (remark.resolved) {
      return '009720';
    }

    switch (remark.category) {
    case 'accidents': return 'FBF514';
    case 'damages': return 'E40521';
    case 'litter': return '9F6807';
    default: return this.defaultRemarkMarkerColor;
    }
  }

  drawMarker(longitude, latitude, title, content, color, enlarge) {
    let position = new google.maps.LatLng(latitude, longitude);
    let infowindow = new google.maps.InfoWindow({
      content: content
    });
    let size = enlarge ? new google.maps.Size(30, 45) : new google.maps.Size(20, 30);
    let marker = new google.maps.Marker({
      position: position,
      title: title,
      map: this.map,
      icon: {
        url: `https://chart.googleapis.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|${color}`,
        scaledSize: size
      }
    });
    marker.addListener('click', function() {
      infowindow.open(map, marker);
    });

    return marker;
  }

  drawRadius() {
    this.radius = new google.maps.Circle({
      strokeColor: '#308AF1',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#308AF1',
      fillOpacity: 0.3,
      map: this.map,
      center: this.position,
      radius: parseFloat(this.filters.radius)
    });
  }
}
<|endoftext|>


<commit_before>test('Function', function () {

  var bound,obj,result;

  obj = { foo: 'bar' };

  bound = (function(num, bool, str, fourth, fifth) {
    equal(this === obj, true, 'Function#bind | Bound object is strictly equal');
    equal(num, 1, 'Function#bind | first parameter');
    equal(bool, true, 'Function#bind | second parameter', { mootools: undefined });
    equal(str, 'wasabi', 'Function#bind | third parameter', { mootools: undefined });
    equal(fourth, 'fourth', 'Function#bind | fourth parameter', { mootools: undefined });
    equal(fifth, 'fifth', 'Function#bind | fifth parameter', { mootools: undefined });
    return 'howdy';
  }).bind(obj, 1, true, 'wasabi');

  result = bound('fourth','fifth');
  equal(result, 'howdy', 'Function#bind | result is correctly returned');

  (function(first) {
    equal(Array.prototype.slice.call(arguments), [], 'Function#bind | arguments array is empty');
    equal(first, undefined, 'Function#bind | first argument is undefined');
  }).bind('foo')();

  bound = (function(num, bool, str) {}).bind('wasabi', 'moo')();

  // Prototype's delay function takes the value in seconds, so 20 makes the tests
  // take at least 20 seconds to finish!
  var delayTime = environment === 'prototype' ? 0.02 : 20;

  async(function(){
    var fn, ref;
    fn = function(one, two) {
      equal(one, 'one', 'Function#delay | first parameter', { mootools: 'two' });
      equal(two, 'two', 'Function#delay | second parameter', { mootools: undefined });
    };
    ref = fn.delay(delayTime, 'one', 'two');
    equal(typeof ref, 'function', 'Function#delay | returns the function', { prototype: 'number', mootools: 'number' });
  });

  async(function(){
    var fn, ref, shouldBeFalse = false;
    fn = function() {
      shouldBeFalse = true;
    };
    fn.delay(delayTime / 4);
    fn.delay(delayTime / 5);
    fn.delay(delayTime / 6);
    ref = fn.cancel();
    equal(ref, fn, 'Function#cancel | returns a reference to the function');
    setTimeout(function() {
      equal(shouldBeFalse, false, 'Function#delay | cancel is working', { prototype: true, mootools: true });
    }, 60);
  });

  async(function(){

    var counter = 0;
    var fn = function(){ counter++; }

    fn.delay(50);
    fn.delay(10);

    setTimeout(function() {
      fn.cancel();
    }, 30);

    setTimeout(function() {
      equal(counter, 1, 'Function#cancel | should be able to find the correct timers');
      fn.cancel();
    }, 60);

  });

  async(function() {
    var counter = 0;
    var expected = [['maybe','a',1],['baby','b',2],['you lazy','c',3],['biotch','d',4]];
    var fn = (function(one, two) {
      equal([this.toString(), one, two], expected[counter], 'Function#lazy | scope and arguments are correct');
      counter++;
    }).lazy();
    fn.call('maybe', 'a', 1);
    fn.call('baby', 'b', 2);
    fn.call('you lazy', 'c', 3);
    equal(counter, 1, "Function#lazy | should have executed once");
    setTimeout(function() {
      equal(counter, 3, 'Function#lazy | was executed by 10ms');
      fn.call('biotch', 'd', 4);
      equal(counter, 4, 'Function#lazy | next execution should be immediate');
    }, 50);
  });

  async(function() {
    var counter = 0;
    var fn = (function() { counter++; }).lazy();
    fn();
    fn();
    fn();
    fn.cancel();
    setTimeout(function() {
      equal(counter, 1, 'Function#lazy | lazy functions can also be canceled');
    }, 10);
  });

  async(function() {
    var counter = 0;
    var fn = (function() { counter++; }).lazy(0.1);
    for(var i = 0; i < 20; i++) {
      fn();
    }
    setTimeout(function() {
      equal(counter, 20, 'Function#lazy | lazy (throttled) functions can have a [wait] value of < 1ms');
    }, 30);
  });

  async(function() {
    var counter = 0;
    var fn = (function() { counter++; }).lazy(0.1, 10);
    for(var i = 0; i < 50; i++) {
      fn();
    }
    setTimeout(function() {
      equal(counter, 10, 'Function#lazy | lazy functions have an upper threshold');
    }, 50);
  });

  async(function() {
    var counter = 0;
    var fn = (function() { counter++; }).lazy(0.1, 1);
    for(var i = 0; i < 50; i++) {
      fn();
    }
    setTimeout(function() {
      equal(counter, 1, 'Function#lazy | lazy functions with a limit of 1 WILL still execute');
    }, 50);
  });

  // Function#debounce

  async(function(){
    var fn, ret, counter = 0, expected = [['leia', 5],['han solo', 7]];
    var fn = (function(one){
      equal([this.toString(), one], expected[counter], 'Function#debounce | scope and arguments are correct');
      counter++;
    }).debounce(30);

    fn.call('3p0', 1);
    fn.call('r2d2', 2);
    fn.call('chewie', 3);

    setTimeout(function() {
      fn.call('leia', 5);
    }, 10);

    setTimeout(function() {
      fn.call('luke', 6);
      fn.call('han solo', 7);
    }, 200);

    ret = fn.call('vader', 4);

    equal(ret, undefined, 'Function#debounce | calls to a debounced function return undefined');

    setTimeout(function() {
      equal(counter, 2, 'Function#debounce | counter is correct');
    }, 500);
  });

  async(function(){
    var fn, ret, counter = 0, expected = [['3p0', 1],['luke', 6]];
    var fn = (function(one){
      equal([this.toString(), one], expected[counter], 'Function#debounce | immediate execution | scope and arguments are correct');
      counter++;
    }).debounce(50, false);

    fn.call('3p0', 1);
    fn.call('r2d2', 2);
    fn.call('chewie', 3);

    setTimeout(function() {
      fn.call('leia', 5);
    }, 10);

    setTimeout(function() {
      fn.call('luke', 6);
      fn.call('han solo', 7);
    }, 100);

    ret = fn.call('vader', 4);

    setTimeout(function() {
      equal(counter, 2, 'Function#debounce | immediate execution | counter is correct');
    }, 200);
  });

  // Function#after

  async(function() {
    var fn, ret, counter = 0, i = 1;
    fn = (function() {
      counter++;
      return 'hooha';
    }).after(5);
    while(i <= 10) {
      ret = fn();
      equal(ret, (i % 5 == 0 ? 'hooha' : undefined), 'Function#after | collects return value as well');
      i++;
    }
    equal(counter, 2, 'Function#after | calls a function only after a certain number of calls');
  });

  // Function#once

  async(function() {
    var fn, obj = { foo:'bar' }, counter = 0;
    fn = (function(one, two) {
      counter++;
      equal(this, obj, 'Function#once | scope is properly set');
      equal(one, 'one', 'Function#once | first argument is passed');
      equal(two, 'two', 'Function#once | second argument is passed');
      return counter * 30;
    }).once();

    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | first call calculates the result');
    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | second call memoizes the result');
    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | third call memoizes the result');
    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | fourth call memoizes the result');
    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | fifth call memoizes the result');

    equal(counter, 1, 'Function#once | counter is only incremented once');
  });

  async(function() {
    var fn, counter = 0;
    fn = (function(one, two) {
      counter++;
    }).once();

    fn.call();
    fn.call();
    fn.call();

    equal(counter, 1, 'Function#once | returning undefined will not affect the number of calls');
  });

});

<commit_msg>loosening up the timeouts just a bit<commit_after>test('Function', function () {

  var bound,obj,result;

  obj = { foo: 'bar' };

  bound = (function(num, bool, str, fourth, fifth) {
    equal(this === obj, true, 'Function#bind | Bound object is strictly equal');
    equal(num, 1, 'Function#bind | first parameter');
    equal(bool, true, 'Function#bind | second parameter', { mootools: undefined });
    equal(str, 'wasabi', 'Function#bind | third parameter', { mootools: undefined });
    equal(fourth, 'fourth', 'Function#bind | fourth parameter', { mootools: undefined });
    equal(fifth, 'fifth', 'Function#bind | fifth parameter', { mootools: undefined });
    return 'howdy';
  }).bind(obj, 1, true, 'wasabi');

  result = bound('fourth','fifth');
  equal(result, 'howdy', 'Function#bind | result is correctly returned');

  (function(first) {
    equal(Array.prototype.slice.call(arguments), [], 'Function#bind | arguments array is empty');
    equal(first, undefined, 'Function#bind | first argument is undefined');
  }).bind('foo')();

  bound = (function(num, bool, str) {}).bind('wasabi', 'moo')();

  // Prototype's delay function takes the value in seconds, so 20 makes the tests
  // take at least 20 seconds to finish!
  var delayTime = environment === 'prototype' ? 0.02 : 20;

  async(function(){
    var fn, ref;
    fn = function(one, two) {
      equal(one, 'one', 'Function#delay | first parameter', { mootools: 'two' });
      equal(two, 'two', 'Function#delay | second parameter', { mootools: undefined });
    };
    ref = fn.delay(delayTime, 'one', 'two');
    equal(typeof ref, 'function', 'Function#delay | returns the function', { prototype: 'number', mootools: 'number' });
  });

  async(function(){
    var fn, ref, shouldBeFalse = false;
    fn = function() {
      shouldBeFalse = true;
    };
    fn.delay(delayTime / 4);
    fn.delay(delayTime / 5);
    fn.delay(delayTime / 6);
    ref = fn.cancel();
    equal(ref, fn, 'Function#cancel | returns a reference to the function');
    setTimeout(function() {
      equal(shouldBeFalse, false, 'Function#delay | cancel is working', { prototype: true, mootools: true });
    }, 60);
  });

  async(function(){

    var counter = 0;
    var fn = function(){ counter++; }

    fn.delay(50);
    fn.delay(10);

    setTimeout(function() {
      fn.cancel();
    }, 30);

    setTimeout(function() {
      equal(counter, 1, 'Function#cancel | should be able to find the correct timers');
      fn.cancel();
    }, 60);

  });

  async(function() {
    var counter = 0;
    var expected = [['maybe','a',1],['baby','b',2],['you lazy','c',3],['biotch','d',4]];
    var fn = (function(one, two) {
      equal([this.toString(), one, two], expected[counter], 'Function#lazy | scope and arguments are correct');
      counter++;
    }).lazy();
    fn.call('maybe', 'a', 1);
    fn.call('baby', 'b', 2);
    fn.call('you lazy', 'c', 3);
    equal(counter, 1, "Function#lazy | should have executed once");
    setTimeout(function() {
      equal(counter, 3, 'Function#lazy | was executed by 10ms');
      fn.call('biotch', 'd', 4);
      equal(counter, 4, 'Function#lazy | next execution should be immediate');
    }, 100);
  });

  async(function() {
    var counter = 0;
    var fn = (function() { counter++; }).lazy();
    fn();
    fn();
    fn();
    fn.cancel();
    setTimeout(function() {
      equal(counter, 1, 'Function#lazy | lazy functions can also be canceled');
    }, 10);
  });

  async(function() {
    var counter = 0;
    var fn = (function() { counter++; }).lazy(0.1);
    for(var i = 0; i < 20; i++) {
      fn();
    }
    setTimeout(function() {
      equal(counter, 20, 'Function#lazy | lazy (throttled) functions can have a [wait] value of < 1ms');
    }, 100);
  });

  async(function() {
    var counter = 0;
    var fn = (function() { counter++; }).lazy(0.1, 10);
    for(var i = 0; i < 50; i++) {
      fn();
    }
    setTimeout(function() {
      equal(counter, 10, 'Function#lazy | lazy functions have an upper threshold');
    }, 50);
  });

  async(function() {
    var counter = 0;
    var fn = (function() { counter++; }).lazy(0.1, 1);
    for(var i = 0; i < 50; i++) {
      fn();
    }
    setTimeout(function() {
      equal(counter, 1, 'Function#lazy | lazy functions with a limit of 1 WILL still execute');
    }, 50);
  });

  // Function#debounce

  async(function(){
    var fn, ret, counter = 0, expected = [['leia', 5],['han solo', 7]];
    var fn = (function(one){
      equal([this.toString(), one], expected[counter], 'Function#debounce | scope and arguments are correct');
      counter++;
    }).debounce(30);

    fn.call('3p0', 1);
    fn.call('r2d2', 2);
    fn.call('chewie', 3);

    setTimeout(function() {
      fn.call('leia', 5);
    }, 10);

    setTimeout(function() {
      fn.call('luke', 6);
      fn.call('han solo', 7);
    }, 200);

    ret = fn.call('vader', 4);

    equal(ret, undefined, 'Function#debounce | calls to a debounced function return undefined');

    setTimeout(function() {
      equal(counter, 2, 'Function#debounce | counter is correct');
    }, 500);
  });

  async(function(){
    var fn, ret, counter = 0, expected = [['3p0', 1],['luke', 6]];
    var fn = (function(one){
      equal([this.toString(), one], expected[counter], 'Function#debounce | immediate execution | scope and arguments are correct');
      counter++;
    }).debounce(50, false);

    fn.call('3p0', 1);
    fn.call('r2d2', 2);
    fn.call('chewie', 3);

    setTimeout(function() {
      fn.call('leia', 5);
    }, 10);

    setTimeout(function() {
      fn.call('luke', 6);
      fn.call('han solo', 7);
    }, 100);

    ret = fn.call('vader', 4);

    setTimeout(function() {
      equal(counter, 2, 'Function#debounce | immediate execution | counter is correct');
    }, 200);
  });

  // Function#after

  async(function() {
    var fn, ret, counter = 0, i = 1;
    fn = (function() {
      counter++;
      return 'hooha';
    }).after(5);
    while(i <= 10) {
      ret = fn();
      equal(ret, (i % 5 == 0 ? 'hooha' : undefined), 'Function#after | collects return value as well');
      i++;
    }
    equal(counter, 2, 'Function#after | calls a function only after a certain number of calls');
  });

  // Function#once

  async(function() {
    var fn, obj = { foo:'bar' }, counter = 0;
    fn = (function(one, two) {
      counter++;
      equal(this, obj, 'Function#once | scope is properly set');
      equal(one, 'one', 'Function#once | first argument is passed');
      equal(two, 'two', 'Function#once | second argument is passed');
      return counter * 30;
    }).once();

    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | first call calculates the result');
    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | second call memoizes the result');
    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | third call memoizes the result');
    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | fourth call memoizes the result');
    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | fifth call memoizes the result');

    equal(counter, 1, 'Function#once | counter is only incremented once');
  });

  async(function() {
    var fn, counter = 0;
    fn = (function(one, two) {
      counter++;
    }).once();

    fn.call();
    fn.call();
    fn.call();

    equal(counter, 1, 'Function#once | returning undefined will not affect the number of calls');
  });

});

<|endoftext|>


<commit_before>define(
  [
    'underscore-min'
  ],
  function() {

    function WString(s,g) {
      this.site = s;
      this.clockValue = g;
      this.chars = [];
      this.queue = [];
    }

    function silence() {}

    var trace = silence;
    var err = console.log;

    WString.prototype = {

      init: function (site, initClockValue, cs, qs) {
        this.site = site;
        this.clockValue = initClockValue;
        this.chars = cs || [];  // Stores WCHAR structures
        this.queue = qs || [];  // Stores OPERATION structures
      },

      genId: function() {
        return { site: this.site, clock: this.tick() };
      },

      endingId: { ending: true },

      beginningId: { beginning: true },

      tick: function () {
        this.clockValue = this.clockValue + 1;
        return this.clockValue;
      },

      visible: function() {
        return _.where(this.chars, { isVisible: true});
      },

      text: function() {
        return  _.pluck(this.visible(), 'alpha').join("");
      },

      ithVisible: function(i) {
        return this.visible()[i];
      },

      prevOf: function(visiblePos) {
        if (visiblePos == 0) return this.beginningId;
        else return this.ithVisible(visiblePos-1).id;
      },

      nextOf: function(visiblePos) {
        if (visiblePos >= this.visible().length) return this.endingId;
        else return this.ithVisible(visiblePos).id; // Not +1 because we are inserting just before this char
      },

      idLessThan: function(a,b) {
        if (a.beginning && b.beginning || a.ending && b.ending) return false;
        else return (
          b.beginning ? false :
            a.beginning ? true :
              b.ending ? true :
                a.ending ? false :
                  (a.site < b.site) || (a.site === b.site && a.clock < b.clock) );
      },

      visibleIndexOf: function(id) {
        return this.indexOf(id, this.visible());
      },

      // The index into `chars` (by default) of the given ID; or -1 if not found.
      indexOf: function(id, col) {
        if (id.beginning) return 0;
        else {
          var cs = col || this.chars;
          if (id.ending) return cs.length;
          else return this.indexWhere(_.pluck(cs, 'id'), function(a) {
            return (a.site === id.site && a.clock === id.clock);
          });
        }
      },

      // The first index in `col` where `pred` is true for an element of `col`; or -1 otherwise.
      indexWhere: function(col, pred) {
        if (false === _.isEmpty(col))
          for (var i=0; i < col.length; i++)
            if (pred(col[i])) return i;

        return -1;
      },

      // Integrate rop into this WString, calling function f after integration if integration happened.
      remoteIntegrate: function(rop, f) {
        trace("Integration of remote op: ", rop);

        // Clone to avoid sharing state between different instances of WString in the same JS interpreter
        // Also, push the function into the operation in case it is queued for later.
        var op = _.extend(_.clone(rop),
          { wchar: _.clone(rop.wchar) },
          f && _.isFunction(f) ? { afterIntegration: f } : {} );

        if (this.canIntegrateOp(op)) {
          if (op.op === "ins") {
            this.integrateIns(op.wchar, op.wchar.prev, op.wchar.next);
            var ipos = this.visibleIndexOf(op.wchar.id);
            if (op.afterIntegration) op.afterIntegration(ipos,op,this);
          }
          else if (op.op == "del") {
            var dpos = this.visibleIndexOf(op.wchar.id);
            this.hide(op.wchar.id);
            if (op.afterIntegration) op.afterIntegration(dpos,op,this);
          }
          else {
            err("Unrecognised op:", op.op, op);
          }
        } else {
          trace("Queueing");
          this.queue.push(op); // mutate
        }
      },

      subseq: function(prev,next) {
        var from = prev.beginning ? 0 : this.indexOf(prev) + 1,
          until = next.ending ? this.chars.length : this.indexOf(next);
        return this.chars.slice(from,until);
      },

      // Id => Boolean
      exists: function(id) {
        return this.indexOf(id) != -1;
      },

      // WChar => Boolean
      canIntegrate: function(wchar) {
        return this.exists(wchar.next) && this.exists(wchar.prev);
      },

      // Op => Boolean
      canIntegrateOp: function(op) {
        return op.op == "ins" ? this.canIntegrate(op.wchar) : this.exists(op.wchar.id);
      },

      // Lowest-level primitive insert into the local character array
      ins: function(wchar, pos) {
        trace("Insert ", wchar, " at ", pos);
        this.chars.splice(pos, 0, wchar); // mutate

        // de-queue a (now valid) waiting operation:
        var op = _.find(this.queue, _.bind(this.canIntegrateOp, this));

        if (op) {

          function eq(x) {
            return x.op === op.op &&
              x.wchar.id.site === op.wchar.id.site &&
              x.wchar.id.clock === op.wchar.id.clock;
          }

          this.queue = _.reject(this.queue, eq); // mutate
          this.remoteIntegrate(op);
        }

      },

      hide: function(id) {
        this.chars[this.indexOf(id)].isVisible = false; // mutate
      },

      // (WChar, Id, Id) => Unit
      integrateIns: function(wchar, before, after) {
        var s = this.subseq(before, after);
        if (_.isEmpty(s)) this.ins(wchar, this.indexOf(after));
        else {

          var L = [before].concat(_.pluck(this.reduce(s),'id'));
          L.push(after);

          var i = 1;
          while (i < (L.length-1) && this.idLessThan(L[i],wchar.id)) i++;

          this.integrateIns(wchar, L[i-1], L[i]);
        }
      },

      // List[Id] => List[Id]
      reduce: function(cs) {

        function idNotEqual(a,b) {
          return (a.beginning && b.beginning) ? false :
            (a.ending && b.ending) ? false :
              (a.site != b.site || a.clock != b.clock);
        }

        return _.filter(cs, function(c) {
          return _.every(cs, function(x) {
            return idNotEqual(x.id, c.next) && idNotEqual(x.id, c.prev);
          })
        })
      },

      // (String, String, Int) => Operation
      localIntegrate: function (op, ch, pos) {

        if (op === "ins") {

          // Generate a new wchar:
          var prevId = this.prevOf(pos), nextId = this.nextOf(pos);
          var newChar = {
            alpha: ch,
            id: this.genId(),
            prev: prevId,
            next: nextId,
            isVisible: true
          };

          // Insert the wchar locally (mutate):
          this.chars.splice(this.indexOf(nextId), 0, newChar);

          return { op: op, from: this.site, wchar: _.clone(newChar) };
        }
        else if (op === "del") {
          var existingChar = this.ithVisible(pos);
          existingChar.isVisible = false; // mutate
          return { op: op, from: this.site, wchar: existingChar };
        }
        else {
          err("Unrecognised local operation type", op);
          return {};
        }

      }

    };

    return WString;
  }
);
<commit_msg>Moved de-queing into a separate function.<commit_after>define(
  [
    'underscore-min'
  ],
  function() {

    function WString(s,g) {
      this.site = s;
      this.clockValue = g;
      this.chars = [];
      this.queue = [];
    }

    function silence() {}

    var trace = silence;
    var err = console.log;

    WString.prototype = {

      init: function (site, initClockValue, cs, qs) {
        this.site = site;
        this.clockValue = initClockValue;
        this.chars = cs || [];  // Stores WCHAR structures
        this.queue = qs || [];  // Stores OPERATION structures
      },

      genId: function() {
        return { site: this.site, clock: this.tick() };
      },

      endingId: { ending: true },

      beginningId: { beginning: true },

      tick: function () {
        this.clockValue = this.clockValue + 1;
        return this.clockValue;
      },

      visible: function() {
        return _.where(this.chars, { isVisible: true});
      },

      text: function() {
        return  _.pluck(this.visible(), 'alpha').join("");
      },

      ithVisible: function(i) {
        return this.visible()[i];
      },

      prevOf: function(visiblePos) {
        if (visiblePos == 0) return this.beginningId;
        else return this.ithVisible(visiblePos-1).id;
      },

      nextOf: function(visiblePos) {
        if (visiblePos >= this.visible().length) return this.endingId;
        else return this.ithVisible(visiblePos).id; // Not +1 because we are inserting just before this char
      },

      idLessThan: function(a,b) {
        if (a.beginning && b.beginning || a.ending && b.ending) return false;
        else return (
          b.beginning ? false :
            a.beginning ? true :
              b.ending ? true :
                a.ending ? false :
                  (a.site < b.site) || (a.site === b.site && a.clock < b.clock) );
      },

      visibleIndexOf: function(id) {
        return this.indexOf(id, this.visible());
      },

      // The index into `chars` (by default) of the given ID; or -1 if not found.
      indexOf: function(id, col) {
        if (id.beginning) return 0;
        else {
          var cs = col || this.chars;
          if (id.ending) return cs.length;
          else return this.indexWhere(_.pluck(cs, 'id'), function(a) {
            return (a.site === id.site && a.clock === id.clock);
          });
        }
      },

     tryDequeue: function() {
       var op = _.find(this.queue, _.bind(this.canIntegrateOp, this));

       if (op) {

         function eq(x) {
           return x.op === op.op &&
             x.wchar.id.site === op.wchar.id.site &&
             x.wchar.id.clock === op.wchar.id.clock;
         }

         this.queue = _.reject(this.queue, eq); // mutate
         this.remoteIntegrate(op);
       }
    },

      // The first index in `col` where `pred` is true for an element of `col`; or -1 otherwise.
      indexWhere: function(col, pred) {
        if (false === _.isEmpty(col))
          for (var i=0; i < col.length; i++)
            if (pred(col[i])) return i;

        return -1;
      },

      // Integrate rop into this WString, calling function f after integration if integration happened.
      remoteIntegrate: function(rop, f) {
        trace("Integration of remote op: ", rop);

        // Clone to avoid sharing state between different instances of WString in the same JS interpreter
        // Also, push the function into the operation in case it is queued for later.
        var op = _.extend(_.clone(rop),
          { wchar: _.clone(rop.wchar) },
          f && _.isFunction(f) ? { afterIntegration: f } : {} );

        if (this.canIntegrateOp(op)) {
          if (op.op === "ins") {
            this.integrateIns(op.wchar, op.wchar.prev, op.wchar.next);
            var ipos = this.visibleIndexOf(op.wchar.id);
            if (op.afterIntegration) op.afterIntegration(ipos,op,this);
            this.tryDequeue();
          }
          else if (op.op == "del") {
            var dpos = this.visibleIndexOf(op.wchar.id);
            this.hide(op.wchar.id);
            if (op.afterIntegration) op.afterIntegration(dpos,op,this);
          }
          else {
            err("Unrecognised op:", op.op, op);
          }
        } else {
          this.queue.push(op); // mutate
        }
      },

      subseq: function(prev,next) {
        var from = prev.beginning ? 0 : this.indexOf(prev) + 1,
          until = next.ending ? this.chars.length : this.indexOf(next);
        return this.chars.slice(from,until);
      },

      // Id => Boolean
      exists: function(id) {
        return this.indexOf(id) != -1;
      },

      // WChar => Boolean
      canIntegrate: function(wchar) {
        return this.exists(wchar.next) && this.exists(wchar.prev);
      },

      // Op => Boolean
      canIntegrateOp: function(op) {
        return op.op == "ins" ? this.canIntegrate(op.wchar) : this.exists(op.wchar.id);
      },

      // Lowest-level primitive insert into the local character array
      ins: function(wchar, pos) {
        trace("Insert ", wchar, " at ", pos);
        this.chars.splice(pos, 0, wchar); // mutate
      },

      hide: function(id) {
        this.chars[this.indexOf(id)].isVisible = false; // mutate
      },

      // (WChar, Id, Id) => Unit
      integrateIns: function(wchar, before, after) {
        var s = this.subseq(before, after);
        if (_.isEmpty(s)) {
          this.ins(wchar, this.indexOf(after));
        }
        else {

          var L = [before].concat(_.pluck(this.reduce(s),'id'));
          L.push(after);

          var i = 1;
          while (i < (L.length-1) && this.idLessThan(L[i],wchar.id)) i++;

          this.integrateIns(wchar, L[i-1], L[i]);
        }
      },

      // List[Id] => List[Id]
      reduce: function(cs) {

        function idNotEqual(a,b) {
          return (a.beginning && b.beginning) ? false :
            (a.ending && b.ending) ? false :
              (a.site != b.site || a.clock != b.clock);
        }

        return _.filter(cs, function(c) {
          return _.every(cs, function(x) {
            return idNotEqual(x.id, c.next) && idNotEqual(x.id, c.prev);
          })
        })
      },

      // (String, String, Int) => Operation
      localIntegrate: function (op, ch, pos) {

        if (op === "ins") {

          // Generate a new wchar:
          var prevId = this.prevOf(pos), nextId = this.nextOf(pos);
          var newChar = {
            alpha: ch,
            id: this.genId(),
            prev: prevId,
            next: nextId,
            isVisible: true
          };

          // Insert the wchar locally (mutate):
          this.chars.splice(this.indexOf(nextId), 0, newChar);

          return { op: op, from: this.site, wchar: _.clone(newChar) };
        }
        else if (op === "del") {
          var existingChar = this.ithVisible(pos);
          existingChar.isVisible = false; // mutate
          return { op: op, from: this.site, wchar: existingChar };
        }
        else {
          err("Unrecognised local operation type", op);
          return {};
        }

      }

    };

    return WString;
  }
);
<|endoftext|>