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);
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;
};
@ -177,54 +190,6 @@ Client.prototype.find = function (channelId) {
Client.prototype.connect = function (args, isStartup = false) {
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({
uuid: args.uuid,
@ -244,12 +209,60 @@ Client.prototype.connect = function (args, isStartup = false) {
saslAccount: String(args.saslAccount || ""),
saslPassword: String(args.saslPassword || ""),
commands: args.commands || [],
channels: channels,
ignoreList: args.ignoreList ? args.ignoreList : [],
});
// Set network lobby channel id
network.channels[0].id = lobbyChannelId;
if (args.channels) {
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.emit("network", {
@ -281,7 +294,7 @@ Client.prototype.connect = function (args, isStartup = false) {
if (!isStartup) {
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) {
_.defaults(this, attr, {
id: 0,
idStorage: 0,
messages: [],
name: "",
key: "",
@ -192,7 +193,7 @@ Chan.prototype.getFilteredClone = function (lastActiveChannel, lastMessage) {
}
newChannel.totalMessages = this[prop].length;
} else {
} else if (prop !== "idStorage") {
newChannel[prop] = this[prop];
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,9 +21,12 @@ try {
const currentSchemaVersion = 1520239200;
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 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)",
// Indexes
"CREATE INDEX IF NOT EXISTS network_channel ON messages (network, channel)",
"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) {
if (!this.isEnabled) {
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) {
if (!this.isEnabled) {
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.
*

View file

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

View file

@ -66,6 +66,12 @@ describe("SQLite Message Storage", function () {
sql:
"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",
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) {
store.database.serialize(() =>
store.database.get(