Merge pull request #1712 from thelounge/xpaw/users-map

Convert users list to map
This commit is contained in:
Jérémie Astori 2017-11-19 11:07:05 -05:00 committed by GitHub
commit da5a5c7175
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 111 additions and 93 deletions

View File

@ -483,7 +483,7 @@ Client.prototype.names = function(data) {
client.emit("names", { client.emit("names", {
id: target.chan.id, id: target.chan.id,
users: target.chan.users, users: target.chan.getSortedUsers(target.network.irc),
}); });
}; };

View File

@ -28,7 +28,7 @@ function Chan(attr) {
firstUnread: 0, firstUnread: 0,
unread: 0, unread: 0,
highlight: 0, highlight: 0,
users: [], users: new Map(),
}); });
} }
@ -97,7 +97,7 @@ Chan.prototype.dereferencePreviews = function(messages) {
}); });
}; };
Chan.prototype.sortUsers = function(irc) { Chan.prototype.getSortedUsers = function(irc) {
var userModeSortPriority = {}; var userModeSortPriority = {};
irc.network.options.PREFIX.forEach((prefix, index) => { irc.network.options.PREFIX.forEach((prefix, index) => {
userModeSortPriority[prefix.symbol] = index; userModeSortPriority[prefix.symbol] = index;
@ -105,7 +105,9 @@ Chan.prototype.sortUsers = function(irc) {
userModeSortPriority[""] = 99; // No mode is lowest userModeSortPriority[""] = 99; // No mode is lowest
this.users = this.users.sort(function(a, b) { const users = Array.from(this.users.values());
return users.sort(function(a, b) {
if (a.mode === b.mode) { if (a.mode === b.mode) {
return a.nick.toLowerCase() < b.nick.toLowerCase() ? -1 : 1; return a.nick.toLowerCase() < b.nick.toLowerCase() ? -1 : 1;
} }
@ -119,13 +121,21 @@ Chan.prototype.findMessage = function(msgId) {
}; };
Chan.prototype.findUser = function(nick) { Chan.prototype.findUser = function(nick) {
return _.find(this.users, {nick: nick}); return this.users.get(nick.toLowerCase());
}; };
Chan.prototype.getUser = function(nick) { Chan.prototype.getUser = function(nick) {
return this.findUser(nick) || new User({nick: nick}); return this.findUser(nick) || new User({nick: nick});
}; };
Chan.prototype.setUser = function(user) {
this.users.set(user.nick.toLowerCase(), user);
};
Chan.prototype.removeUser = function(user) {
this.users.delete(user.nick.toLowerCase());
};
Chan.prototype.toJSON = function() { Chan.prototype.toJSON = function() {
var clone = _.clone(this); var clone = _.clone(this);
clone.users = []; // Do not send user list, the client will explicitly request it when needed clone.users = []; // Do not send user list, the client will explicitly request it when needed

View File

@ -1,6 +1,5 @@
"use strict"; "use strict";
const _ = require("lodash");
const Msg = require("../../models/msg"); const Msg = require("../../models/msg");
module.exports = function(irc, network) { module.exports = function(irc, network) {
@ -9,7 +8,7 @@ module.exports = function(irc, network) {
const away = data.message; const away = data.message;
network.channels.forEach((chan) => { network.channels.forEach((chan) => {
const user = _.find(chan.users, {nick: data.nick}); const user = chan.findUser(data.nick);
if (!user || user.away === away) { if (!user || user.away === away) {
return; return;

View File

@ -35,8 +35,7 @@ module.exports = function(irc, network) {
}); });
chan.pushMessage(client, msg); chan.pushMessage(client, msg);
chan.users.push(user); chan.setUser(new User({nick: data.nick}));
chan.sortUsers(irc);
client.emit("users", { client.emit("users", {
chan: chan.id, chan: chan.id,
}); });

View File

@ -1,6 +1,5 @@
"use strict"; "use strict";
const _ = require("lodash");
const Msg = require("../../models/msg"); const Msg = require("../../models/msg");
module.exports = function(irc, network) { module.exports = function(irc, network) {
@ -25,9 +24,9 @@ module.exports = function(irc, network) {
chan.pushMessage(client, msg); chan.pushMessage(client, msg);
if (data.kicked === irc.user.nick) { if (data.kicked === irc.user.nick) {
chan.users = []; chan.users = new Map();
} else { } else {
chan.users = _.without(chan.users, msg.target); chan.removeUser(msg.target);
} }
client.emit("users", { client.emit("users", {

View File

@ -113,8 +113,6 @@ module.exports = function(irc, network) {
// TODO: This is horrible // TODO: This is horrible
irc.raw("NAMES", data.target); irc.raw("NAMES", data.target);
} else { } else {
targetChan.sortUsers(irc);
client.emit("users", { client.emit("users", {
chan: targetChan.id, chan: targetChan.id,
}); });

View File

@ -1,7 +1,5 @@
"use strict"; "use strict";
const User = require("../../models/user");
module.exports = function(irc, network) { module.exports = function(irc, network) {
const client = this; const client = this;
@ -11,31 +9,16 @@ module.exports = function(irc, network) {
return; return;
} }
// Create lookup map of current users, const newUsers = new Map();
// as we need to keep certain properties
// and we can recycle existing User objects
const oldUsers = new Map();
chan.users.forEach((user) => { data.users.forEach((user) => {
oldUsers.set(user.nick, user); const newUser = chan.getUser(user.nick);
newUser.setModes(user.modes, network.prefixLookup);
newUsers.set(user.nick.toLowerCase(), newUser);
}); });
chan.users = data.users.map((user) => { chan.users = newUsers;
const oldUser = oldUsers.get(user.nick);
// For existing users, we only need to update mode
if (oldUser) {
oldUser.setModes(user.modes, network.prefixLookup);
return oldUser;
}
return new User({
nick: user.nick,
modes: user.modes,
}, network.prefixLookup);
});
chan.sortUsers(irc);
client.emit("users", { client.emit("users", {
chan: chan.id, chan: chan.id,

View File

@ -32,6 +32,10 @@ module.exports = function(irc, network) {
return; return;
} }
chan.removeUser(user);
user.nick = data.new_nick;
chan.setUser(user);
msg = new Msg({ msg = new Msg({
time: data.time, time: data.time,
from: user, from: user,
@ -43,7 +47,6 @@ module.exports = function(irc, network) {
user.nick = data.new_nick; user.nick = data.new_nick;
chan.sortUsers(irc);
client.emit("users", { client.emit("users", {
chan: chan.id, chan: chan.id,
}); });

View File

@ -32,7 +32,7 @@ module.exports = function(irc, network) {
}); });
chan.pushMessage(client, msg); chan.pushMessage(client, msg);
chan.users = _.without(chan.users, user); chan.removeUser(user);
client.emit("users", { client.emit("users", {
chan: chan.id, chan: chan.id,
}); });

View File

@ -1,6 +1,5 @@
"use strict"; "use strict";
const _ = require("lodash");
const Msg = require("../../models/msg"); const Msg = require("../../models/msg");
module.exports = function(irc, network) { module.exports = function(irc, network) {
@ -23,7 +22,7 @@ module.exports = function(irc, network) {
}); });
chan.pushMessage(client, msg); chan.pushMessage(client, msg);
chan.users = _.without(chan.users, user); chan.removeUser(user);
client.emit("users", { client.emit("users", {
chan: chan.id, chan: chan.id,
}); });

View File

@ -1,12 +1,31 @@
"use strict"; "use strict";
var expect = require("chai").expect; const expect = require("chai").expect;
const Chan = require("../../src/models/chan");
var Chan = require("../../src/models/chan"); const Msg = require("../../src/models/msg");
var Msg = require("../../src/models/msg"); const User = require("../../src/models/user");
var User = require("../../src/models/user");
describe("Chan", function() { describe("Chan", function() {
const network = {
network: {
options: {
PREFIX: [
{symbol: "~", mode: "q"},
{symbol: "&", mode: "a"},
{symbol: "@", mode: "o"},
{symbol: "%", mode: "h"},
{symbol: "+", mode: "v"},
],
},
},
};
const prefixLookup = {};
network.network.options.PREFIX.forEach((mode) => {
prefixLookup[mode.mode] = mode.symbol;
});
describe("#findMessage(id)", function() { describe("#findMessage(id)", function() {
const chan = new Chan({ const chan = new Chan({
messages: [ messages: [
@ -27,40 +46,51 @@ describe("Chan", function() {
}); });
}); });
describe("#sortUsers(irc)", function() { describe("#setUser(user)", function() {
var network = { it("should make key lowercase", function() {
network: { const chan = new Chan();
options: { chan.setUser(new User({nick: "TestUser"}));
PREFIX: [
{symbol: "~", mode: "q"},
{symbol: "&", mode: "a"},
{symbol: "@", mode: "o"},
{symbol: "%", mode: "h"},
{symbol: "+", mode: "v"},
],
},
},
};
var prefixLookup = {}; expect(chan.users.has("testuser")).to.be.true;
network.network.options.PREFIX.forEach((mode) => {
prefixLookup[mode.mode] = mode.symbol;
}); });
var makeUser = function(nick) { it("should update user object", function() {
return new User({nick: nick}, prefixLookup); const chan = new Chan();
}; chan.setUser(new User({nick: "TestUser"}, prefixLookup));
chan.setUser(new User({nick: "TestUseR", modes: ["o"]}, prefixLookup));
const user = chan.getUser("TestUSER");
expect(user.mode).to.equal("@");
});
});
describe("#getUser(nick)", function() {
it("should returning existing object", function() {
const chan = new Chan();
chan.setUser(new User({nick: "TestUseR", modes: ["o"]}, prefixLookup));
const user = chan.getUser("TestUSER");
expect(user.mode).to.equal("@");
});
it("should make new User object if not found", function() {
const chan = new Chan();
const user = chan.getUser("very-testy-user");
expect(user.nick).to.equal("very-testy-user");
});
});
describe("#getSortedUsers(irc)", function() {
var getUserNames = function(chan) { var getUserNames = function(chan) {
return chan.users.map((u) => u.nick); return chan.getSortedUsers(network).map((u) => u.nick);
}; };
it("should sort a simple user list", function() { it("should sort a simple user list", function() {
var chan = new Chan({users: [ const chan = new Chan();
[
"JocelynD", "YaManicKill", "astorije", "xPaw", "Max-P", "JocelynD", "YaManicKill", "astorije", "xPaw", "Max-P",
].map(makeUser)}); ].forEach((nick) => chan.setUser(new User({nick: nick}, prefixLookup)));
chan.sortUsers(network);
expect(getUserNames(chan)).to.deep.equal([ expect(getUserNames(chan)).to.deep.equal([
"astorije", "JocelynD", "Max-P", "xPaw", "YaManicKill", "astorije", "JocelynD", "Max-P", "xPaw", "YaManicKill",
@ -68,14 +98,12 @@ describe("Chan", function() {
}); });
it("should group users by modes", function() { it("should group users by modes", function() {
var chan = new Chan({users: [ const chan = new Chan();
new User({nick: "JocelynD", modes: ["a", "o"]}, prefixLookup), chan.setUser(new User({nick: "JocelynD", modes: ["a", "o"]}, prefixLookup));
new User({nick: "YaManicKill", modes: ["v"]}, prefixLookup), chan.setUser(new User({nick: "YaManicKill", modes: ["v"]}, prefixLookup));
new User({nick: "astorije", modes: ["h"]}, prefixLookup), chan.setUser(new User({nick: "astorije", modes: ["h"]}, prefixLookup));
new User({nick: "xPaw", modes: ["q"]}, prefixLookup), chan.setUser(new User({nick: "xPaw", modes: ["q"]}, prefixLookup));
new User({nick: "Max-P", modes: ["o"]}, prefixLookup), chan.setUser(new User({nick: "Max-P", modes: ["o"]}, prefixLookup));
]});
chan.sortUsers(network);
expect(getUserNames(chan)).to.deep.equal([ expect(getUserNames(chan)).to.deep.equal([
"xPaw", "JocelynD", "Max-P", "astorije", "YaManicKill", "xPaw", "JocelynD", "Max-P", "astorije", "YaManicKill",
@ -83,14 +111,12 @@ describe("Chan", function() {
}); });
it("should sort a mix of users and modes", function() { it("should sort a mix of users and modes", function() {
var chan = new Chan({users: [ const chan = new Chan();
new User({nick: "JocelynD"}, prefixLookup), chan.setUser(new User({nick: "JocelynD"}, prefixLookup));
new User({nick: "YaManicKill", modes: ["o"]}, prefixLookup), chan.setUser(new User({nick: "YaManicKill", modes: ["o"]}, prefixLookup));
new User({nick: "astorije"}, prefixLookup), chan.setUser(new User({nick: "astorije"}, prefixLookup));
new User({nick: "xPaw"}, prefixLookup), chan.setUser(new User({nick: "xPaw"}, prefixLookup));
new User({nick: "Max-P", modes: ["o"]}, prefixLookup), chan.setUser(new User({nick: "Max-P", modes: ["o"]}, prefixLookup));
]});
chan.sortUsers(network);
expect(getUserNames(chan)).to.deep.equal( expect(getUserNames(chan)).to.deep.equal(
["Max-P", "YaManicKill", "astorije", "JocelynD", "xPaw"] ["Max-P", "YaManicKill", "astorije", "JocelynD", "xPaw"]
@ -98,18 +124,20 @@ describe("Chan", function() {
}); });
it("should be case-insensitive", function() { it("should be case-insensitive", function() {
var chan = new Chan({users: ["aB", "Ad", "AA", "ac"].map(makeUser)}); const chan = new Chan();
chan.sortUsers(network); [
"aB", "Ad", "AA", "ac",
].forEach((nick) => chan.setUser(new User({nick: nick}, prefixLookup)));
expect(getUserNames(chan)).to.deep.equal(["AA", "aB", "ac", "Ad"]); expect(getUserNames(chan)).to.deep.equal(["AA", "aB", "ac", "Ad"]);
}); });
it("should parse special characters successfully", function() { it("should parse special characters successfully", function() {
var chan = new Chan({users: [ const chan = new Chan();
[
"[foo", "]foo", "(foo)", "{foo}", "<foo>", "_foo", "@foo", "^foo", "[foo", "]foo", "(foo)", "{foo}", "<foo>", "_foo", "@foo", "^foo",
"&foo", "!foo", "+foo", "Foo", "&foo", "!foo", "+foo", "Foo",
].map(makeUser)}); ].forEach((nick) => chan.setUser(new User({nick: nick}, prefixLookup)));
chan.sortUsers(network);
expect(getUserNames(chan)).to.deep.equal([ expect(getUserNames(chan)).to.deep.equal([
"!foo", "&foo", "(foo)", "+foo", "<foo>", "@foo", "[foo", "]foo", "!foo", "&foo", "(foo)", "+foo", "<foo>", "@foo", "[foo", "]foo",