Create channels table in sqlite, and store channels in it

This commit is contained in:
Pavel Djundik 2020-08-06 22:22:51 +03:00
parent 2a11c07ba9
commit d7ed8a38a3
12 changed files with 225 additions and 91 deletions

View file

@ -141,10 +141,23 @@ function Client(manager, name, config = {}) {
} }
} }
Client.prototype.createChannel = function (attr) { Client.prototype.createChannel = function (attr, network) {
const chan = new Chan(attr); const chan = new Chan(attr);
chan.id = this.idChan++; chan.id = this.idChan++;
if (!chan.isLoggable() || !network) {
return chan;
}
const messageStorage = this.messageStorage.find((s) => s.canProvideMessages());
if (messageStorage) {
messageStorage
.getChannelId(network, chan)
.then((id) => (chan.idStorage = id))
.catch((err) => log.error(`Failed to get storage channel id: ${err}`));
}
return chan; return chan;
}; };
@ -177,54 +190,6 @@ Client.prototype.find = function (channelId) {
Client.prototype.connect = function (args, isStartup = false) { Client.prototype.connect = function (args, isStartup = false) {
const client = this; const client = this;
let channels = [];
// Get channel id for lobby before creating other channels for nicer ids
const lobbyChannelId = client.idChan++;
if (args.channels) {
let badName = false;
args.channels.forEach((chan) => {
if (!chan.name) {
badName = true;
return;
}
channels.push(
client.createChannel({
name: chan.name,
key: chan.key || "",
type: chan.type,
})
);
});
if (badName && client.name) {
log.warn(
"User '" +
client.name +
"' on network '" +
args.name +
"' has an invalid channel which has been ignored"
);
}
// `join` is kept for backwards compatibility when updating from versions <2.0
// also used by the "connect" window
} else if (args.join) {
channels = args.join
.replace(/,/g, " ")
.split(/\s+/g)
.map((chan) => {
if (!chan.match(/^[#&!+]/)) {
chan = `#${chan}`;
}
return client.createChannel({
name: chan,
});
});
}
const network = new Network({ const network = new Network({
uuid: args.uuid, uuid: args.uuid,
@ -244,12 +209,60 @@ Client.prototype.connect = function (args, isStartup = false) {
saslAccount: String(args.saslAccount || ""), saslAccount: String(args.saslAccount || ""),
saslPassword: String(args.saslPassword || ""), saslPassword: String(args.saslPassword || ""),
commands: args.commands || [], commands: args.commands || [],
channels: channels,
ignoreList: args.ignoreList ? args.ignoreList : [], ignoreList: args.ignoreList ? args.ignoreList : [],
}); });
// Set network lobby channel id if (args.channels) {
network.channels[0].id = lobbyChannelId; let badName = false;
args.channels.forEach((chan) => {
if (!chan.name) {
badName = true;
return;
}
network.channels.push(
client.createChannel(
{
name: chan.name,
key: chan.key || "",
type: chan.type,
},
network
)
);
});
if (badName && client.name) {
log.warn(
"User '" +
client.name +
"' on network '" +
args.name +
"' has an invalid channel which has been ignored"
);
}
} else if (args.join) {
// `join` is kept for backwards compatibility when updating from versions <2.0
// also used by the "connect" window
args.join
.replace(/,/g, " ")
.split(/\s+/g)
.forEach((chan) => {
if (!chan.match(/^[#&!+]/)) {
chan = `#${chan}`;
}
network.channels.push(
client.createChannel(
{
name: chan,
},
network
)
);
});
}
client.networks.push(network); client.networks.push(network);
client.emit("network", { client.emit("network", {
@ -281,7 +294,7 @@ Client.prototype.connect = function (args, isStartup = false) {
if (!isStartup) { if (!isStartup) {
client.save(); client.save();
channels.forEach((channel) => channel.loadMessages(client, network)); network.channels.forEach((channel) => channel.loadMessages(client, network));
} }
}; };

View file

@ -31,6 +31,7 @@ Chan.State = {
function Chan(attr) { function Chan(attr) {
_.defaults(this, attr, { _.defaults(this, attr, {
id: 0, id: 0,
idStorage: 0,
messages: [], messages: [],
name: "", name: "",
key: "", key: "",
@ -192,7 +193,7 @@ Chan.prototype.getFilteredClone = function (lastActiveChannel, lastMessage) {
} }
newChannel.totalMessages = this[prop].length; newChannel.totalMessages = this[prop].length;
} else { } else if (prop !== "idStorage") {
newChannel[prop] = this[prop]; newChannel[prop] = this[prop];
} }

View file

@ -118,12 +118,15 @@ exports.input = function (network, chan, cmd, args) {
let newChan = network.getChannel(chanName); let newChan = network.getChannel(chanName);
if (typeof newChan === "undefined") { if (typeof newChan === "undefined") {
newChan = client.createChannel({ newChan = client.createChannel(
type: Chan.Type.SPECIAL, {
special: Chan.SpecialType.IGNORELIST, type: Chan.Type.SPECIAL,
name: chanName, special: Chan.SpecialType.IGNORELIST,
data: ignored, name: chanName,
}); data: ignored,
},
network
);
client.emit("join", { client.emit("join", {
network: network.uuid, network: network.uuid,
chan: newChan.getFilteredClone(true), chan: newChan.getFilteredClone(true),

View file

@ -63,10 +63,13 @@ exports.input = function (network, chan, cmd, args) {
} }
} }
const newChan = this.createChannel({ const newChan = this.createChannel(
type: Chan.Type.QUERY, {
name: targetName, type: Chan.Type.QUERY,
}); name: targetName,
},
network
);
this.emit("join", { this.emit("join", {
network: network.uuid, network: network.uuid,

View file

@ -11,10 +11,13 @@ module.exports = function (irc, network) {
let chan = network.getChannel(data.channel); let chan = network.getChannel(data.channel);
if (typeof chan === "undefined") { if (typeof chan === "undefined") {
chan = client.createChannel({ chan = client.createChannel(
name: data.channel, {
state: Chan.State.JOINED, name: data.channel,
}); state: Chan.State.JOINED,
},
network
);
client.emit("join", { client.emit("join", {
network: network.uuid, network: network.uuid,

View file

@ -34,12 +34,15 @@ module.exports = function (irc, network) {
let chan = network.getChannel("Channel List"); let chan = network.getChannel("Channel List");
if (typeof chan === "undefined") { if (typeof chan === "undefined") {
chan = client.createChannel({ chan = client.createChannel(
type: Chan.Type.SPECIAL, {
special: Chan.SpecialType.CHANNELLIST, type: Chan.Type.SPECIAL,
name: "Channel List", special: Chan.SpecialType.CHANNELLIST,
data: msg, name: "Channel List",
}); data: msg,
},
network
);
client.emit("join", { client.emit("join", {
network: network.uuid, network: network.uuid,

View file

@ -75,10 +75,13 @@ module.exports = function (irc, network) {
showInActive = true; showInActive = true;
chan = network.channels[0]; chan = network.channels[0];
} else { } else {
chan = client.createChannel({ chan = client.createChannel(
type: Chan.Type.QUERY, {
name: target, type: Chan.Type.QUERY,
}); name: target,
},
network
);
client.emit("join", { client.emit("join", {
network: network.uuid, network: network.uuid,

View file

@ -50,12 +50,15 @@ module.exports = function (irc, network) {
let chan = network.getChannel(chanName); let chan = network.getChannel(chanName);
if (typeof chan === "undefined") { if (typeof chan === "undefined") {
chan = client.createChannel({ chan = client.createChannel(
type: Chan.Type.SPECIAL, {
special: type, type: Chan.Type.SPECIAL,
name: chanName, special: type,
data: data, name: chanName,
}); data: data,
},
network
);
client.emit("join", { client.emit("join", {
network: network.uuid, network: network.uuid,
chan: chan.getFilteredClone(true), chan: chan.getFilteredClone(true),

View file

@ -22,10 +22,13 @@ module.exports = function (irc, network) {
if (data.error) { if (data.error) {
chan = network.channels[0]; chan = network.channels[0];
} else { } else {
chan = client.createChannel({ chan = client.createChannel(
type: Chan.Type.QUERY, {
name: data.nick, type: Chan.Type.QUERY,
}); name: data.nick,
},
network
);
client.emit("join", { client.emit("join", {
shouldOpen: true, shouldOpen: true,

View file

@ -21,9 +21,12 @@ try {
const currentSchemaVersion = 1520239200; const currentSchemaVersion = 1520239200;
const schema = [ const schema = [
// Schema version #1 // Tables
"CREATE TABLE IF NOT EXISTS options (name TEXT, value TEXT, CONSTRAINT name_unique UNIQUE (name))", "CREATE TABLE IF NOT EXISTS options (name TEXT, value TEXT, CONSTRAINT name_unique UNIQUE (name))",
"CREATE TABLE IF NOT EXISTS channels (channel_id INTEGER PRIMARY KEY, network TEXT, channel TEXT, CONSTRAINT unique_channel UNIQUE (network, channel))",
"CREATE TABLE IF NOT EXISTS messages (network TEXT, channel TEXT, time INTEGER, type TEXT, msg TEXT)", "CREATE TABLE IF NOT EXISTS messages (network TEXT, channel TEXT, time INTEGER, type TEXT, msg TEXT)",
// Indexes
"CREATE INDEX IF NOT EXISTS network_channel ON messages (network, channel)", "CREATE INDEX IF NOT EXISTS network_channel ON messages (network, channel)",
"CREATE INDEX IF NOT EXISTS time ON messages (time)", "CREATE INDEX IF NOT EXISTS time ON messages (time)",
]; ];
@ -116,6 +119,13 @@ class MessageStorage {
}); });
} }
/**
* Store a new message in specified channel
*
* @param Network network - Network object where the channel is
* @param Chan channel - Channel object
* @param Msg msg - Message object to store
*/
index(network, channel, msg) { index(network, channel, msg) {
if (!this.isEnabled) { if (!this.isEnabled) {
return; return;
@ -144,6 +154,12 @@ class MessageStorage {
); );
} }
/**
* Delete stored all stored messages in a channel
*
* @param Network network - Network object where the channel is
* @param Chan channel - Channel object
*/
deleteChannel(network, channel) { deleteChannel(network, channel) {
if (!this.isEnabled) { if (!this.isEnabled) {
return; return;
@ -158,6 +174,51 @@ class MessageStorage {
); );
} }
/**
* Get the stored channel id, creates one if does not exist.
*
* @param Network network - Network object where the channel is
* @param Chan channel - Channel object
*/
getChannelId(network, channel) {
if (!this.isEnabled) {
return Promise.resolve(0);
}
const channelName = channel.name.toLowerCase();
return new Promise((resolve, reject) => {
this.database.serialize(() =>
this.database.get(
"SELECT channel_id FROM channels WHERE network = ? AND channel = ?",
[network.uuid, channelName],
(err, row) => {
if (err) {
return reject(err);
}
if (row) {
return resolve(row.channel_id);
}
// This channel was not found, create it and "recursively" call getChannelId again
this.database.run(
"INSERT INTO channels (network, channel) VALUES (?, ?)",
[network.uuid, channelName],
(err2) => {
if (err2) {
return reject(err2);
}
this.getChannelId(network, channel).then(resolve).catch(reject);
}
);
}
)
);
});
}
/** /**
* Load messages for given channel on a given network and resolve a promise with loaded messages. * Load messages for given channel on a given network and resolve a promise with loaded messages.
* *

View file

@ -18,9 +18,10 @@ module.exports = class PublicClient {
/** /**
* *
* @param {Object} attributes * @param {Object} attributes
* @param {Network} network
*/ */
createChannel(attributes) { createChannel(attributes, network) {
return this.client.createChannel(attributes); return this.client.createChannel(attributes, network);
} }
/** /**

View file

@ -66,6 +66,12 @@ describe("SQLite Message Storage", function () {
sql: sql:
"CREATE TABLE options (name TEXT, value TEXT, CONSTRAINT name_unique UNIQUE (name))", "CREATE TABLE options (name TEXT, value TEXT, CONSTRAINT name_unique UNIQUE (name))",
}, },
{
name: "channels",
tbl_name: "channels",
sql:
"CREATE TABLE channels (channel_id INTEGER PRIMARY KEY, network TEXT, channel TEXT, CONSTRAINT unique_channel UNIQUE (network, channel))",
},
{ {
name: "messages", name: "messages",
tbl_name: "messages", tbl_name: "messages",
@ -80,6 +86,37 @@ describe("SQLite Message Storage", function () {
); );
}); });
it("should create channel id if not exists", function (done) {
const network = {
uuid: "network-uuid",
};
store
.getChannelId(network, {
name: "#This-Is-Channel-One",
})
.then((id) => {
expect(id).to.equal(1);
store
.getChannelId(network, {
name: "#this-is-channel-ONE",
})
.then((id2) => {
expect(id2).to.equal(1);
store
.getChannelId(network, {
name: "#this-is-channel-two",
})
.then((id3) => {
expect(id3).to.equal(2);
done();
});
});
});
});
it("should insert schema version to options table", function (done) { it("should insert schema version to options table", function (done) {
store.database.serialize(() => store.database.serialize(() =>
store.database.get( store.database.get(