Import upstream patches from The Lounge (Feb 2024), bump version to v4.4.1-2 #2
@ -11,8 +11,10 @@ import type {
|
|||||||
SearchResponse,
|
SearchResponse,
|
||||||
SearchQuery,
|
SearchQuery,
|
||||||
SearchableMessageStorage,
|
SearchableMessageStorage,
|
||||||
|
DeletionRequest
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import Network from "../../models/network";
|
import Network from "../../models/network";
|
||||||
|
import { threadId } from "worker_threads";
|
||||||
|
|
||||||
// TODO; type
|
// TODO; type
|
||||||
let sqlite3: any;
|
let sqlite3: any;
|
||||||
@ -244,6 +246,10 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
|||||||
await this.serialize_run("VACUUM");
|
await this.serialize_run("VACUUM");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async vacuum() {
|
||||||
|
await this.serialize_run("VACUUM");
|
||||||
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
if (!this.isEnabled) {
|
if (!this.isEnabled) {
|
||||||
return;
|
return;
|
||||||
@ -499,6 +505,33 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteMessages(req: DeletionRequest): Promise<number> {
|
||||||
|
await this.initDone.promise;
|
||||||
|
let sql = "delete from messages where id in (select id from messages where\n";
|
||||||
|
|
||||||
|
// We roughly get a timestamp from N days before.
|
||||||
|
// We don't adjust for daylight savings time or other weird time jumps
|
||||||
|
const millisecondsInDay = 24 * 60 * 60 * 1000;
|
||||||
|
const deleteBefore = Date.now() - req.olderThanDays * millisecondsInDay;
|
||||||
|
sql += `time <= ${deleteBefore}\n`;
|
||||||
|
|
||||||
|
let typeClause = "";
|
||||||
|
|
||||||
|
if (req.messageTypes !== null) {
|
||||||
|
typeClause = `type in (${req.messageTypes.map((type) => `'${type}'`).join(",")})\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeClause) {
|
||||||
|
sql += `and ${typeClause}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += "order by time asc\n";
|
||||||
|
sql += `limit ${req.limit}\n`;
|
||||||
|
sql += ")";
|
||||||
|
|
||||||
|
return this.serialize_run(sql);
|
||||||
|
}
|
||||||
|
|
||||||
canProvideMessages() {
|
canProvideMessages() {
|
||||||
return this.isEnabled;
|
return this.isEnabled;
|
||||||
}
|
}
|
||||||
@ -506,13 +539,13 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
|||||||
private serialize_run(stmt: string, ...params: any[]): Promise<number> {
|
private serialize_run(stmt: string, ...params: any[]): Promise<number> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.database.serialize(() => {
|
this.database.serialize(() => {
|
||||||
this.database.run(stmt, params, (err) => {
|
this.database.run(stmt, params, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve();
|
resolve(this.changes); // number of affected rows, 'this' is re-bound by sqlite3
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
7
server/plugins/messageStorage/types.d.ts
vendored
7
server/plugins/messageStorage/types.d.ts
vendored
@ -4,6 +4,13 @@ import {Channel} from "../../models/channel";
|
|||||||
import {Message} from "../../models/message";
|
import {Message} from "../../models/message";
|
||||||
import {Network} from "../../models/network";
|
import {Network} from "../../models/network";
|
||||||
import Client from "../../client";
|
import Client from "../../client";
|
||||||
|
import type {MessageType} from "../../models/msg";
|
||||||
|
|
||||||
|
export type DeletionRequest = {
|
||||||
|
olderThanDays: number;
|
||||||
|
messageTypes: MessageType[] | null; //null means no restriction
|
||||||
|
limit: number; //-1 means unlimited
|
||||||
|
}
|
||||||
|
|
||||||
interface MessageStorage {
|
interface MessageStorage {
|
||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
|
@ -12,6 +12,7 @@ import MessageStorage, {
|
|||||||
rollbacks,
|
rollbacks,
|
||||||
} from "../../server/plugins/messageStorage/sqlite";
|
} from "../../server/plugins/messageStorage/sqlite";
|
||||||
import sqlite3 from "sqlite3";
|
import sqlite3 from "sqlite3";
|
||||||
|
import {DeletionRequest} from "../../server/plugins/messageStorage/types"
|
||||||
|
|
||||||
const orig_schema = [
|
const orig_schema = [
|
||||||
// Schema version #1
|
// Schema version #1
|
||||||
@ -127,6 +128,112 @@ describe("SQLite migrations", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("SQLite unit tests", function () {
|
||||||
|
let store: MessageStorage;
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
store = new MessageStorage("testUser");
|
||||||
|
await store._enable(":memory:");
|
||||||
|
store.initDone.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
await store.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deletes messages when asked to", async function () {
|
||||||
|
const baseDate = new Date();
|
||||||
|
|
||||||
|
const net = {uuid: "testnet"} as any;
|
||||||
|
const chan = {name: "#channel"} as any;
|
||||||
|
|
||||||
|
for (let i = 0; i < 14; ++i) {
|
||||||
|
await store.index(
|
||||||
|
net,
|
||||||
|
chan,
|
||||||
|
new Msg({
|
||||||
|
time: dateAddDays(baseDate, -i),
|
||||||
|
text: `msg ${i}`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = 1;
|
||||||
|
const delReq: DeletionRequest = {
|
||||||
|
messageTypes: [MessageType.MESSAGE],
|
||||||
|
limit: limit,
|
||||||
|
olderThanDays: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let deleted = await store.deleteMessages(delReq);
|
||||||
|
expect(deleted).to.equal(limit, "number of deleted messages doesn't match");
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
let messages = await store.getMessages(net, chan, () => id++);
|
||||||
|
expect(messages.find((m) => m.text === "msg 13")).to.be.undefined; // oldest gets deleted first
|
||||||
|
|
||||||
|
// let's test if it properly cleans now
|
||||||
|
delReq.limit = 100;
|
||||||
|
deleted = await store.deleteMessages(delReq);
|
||||||
|
expect(deleted).to.equal(11, "number of deleted messages doesn't match");
|
||||||
|
messages = await store.getMessages(net, chan, () => id++);
|
||||||
|
expect(messages.map((m) => m.text)).to.have.ordered.members(["msg 1", "msg 0"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deletes only the types it should", async function () {
|
||||||
|
const baseDate = new Date();
|
||||||
|
|
||||||
|
const net = {uuid: "testnet"} as any;
|
||||||
|
const chan = {name: "#channel"} as any;
|
||||||
|
|
||||||
|
for (let i = 0; i < 6; ++i) {
|
||||||
|
await store.index(
|
||||||
|
net,
|
||||||
|
chan,
|
||||||
|
new Msg({
|
||||||
|
time: dateAddDays(baseDate, -i),
|
||||||
|
text: `msg ${i}`,
|
||||||
|
type: [
|
||||||
|
MessageType.ACTION,
|
||||||
|
MessageType.AWAY,
|
||||||
|
MessageType.JOIN,
|
||||||
|
MessageType.PART,
|
||||||
|
MessageType.KICK,
|
||||||
|
MessageType.MESSAGE,
|
||||||
|
][i],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const delReq: DeletionRequest = {
|
||||||
|
messageTypes: [MessageType.ACTION, MessageType.JOIN, MessageType.KICK],
|
||||||
|
limit: 100, // effectively no limit
|
||||||
|
olderThanDays: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let deleted = await store.deleteMessages(delReq);
|
||||||
|
expect(deleted).to.equal(3, "number of deleted messages doesn't match");
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
let messages = await store.getMessages(net, chan, () => id++);
|
||||||
|
expect(messages.map((m) => m.type)).to.have.ordered.members([
|
||||||
|
MessageType.MESSAGE,
|
||||||
|
MessageType.PART,
|
||||||
|
MessageType.AWAY,
|
||||||
|
]);
|
||||||
|
|
||||||
|
delReq.messageTypes = [
|
||||||
|
MessageType.JOIN, // this is not in the remaining set, just here as a dummy
|
||||||
|
MessageType.PART,
|
||||||
|
MessageType.MESSAGE,
|
||||||
|
];
|
||||||
|
deleted = await store.deleteMessages(delReq);
|
||||||
|
expect(deleted).to.equal(2, "number of deleted messages doesn't match");
|
||||||
|
messages = await store.getMessages(net, chan, () => id++);
|
||||||
|
expect(messages.map((m) => m.type)).to.have.ordered.members([MessageType.AWAY]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("SQLite Message Storage", function () {
|
describe("SQLite Message Storage", function () {
|
||||||
// Increase timeout due to unpredictable I/O on CI services
|
// Increase timeout due to unpredictable I/O on CI services
|
||||||
this.timeout(util.isRunningOnCI() ? 25000 : 5000);
|
this.timeout(util.isRunningOnCI() ? 25000 : 5000);
|
||||||
@ -388,3 +495,9 @@ describe("SQLite Message Storage", function () {
|
|||||||
expect(fs.existsSync(expectedPath)).to.be.true;
|
expect(fs.existsSync(expectedPath)).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function dateAddDays(date: Date, days: number) {
|
||||||
|
const ret = new Date(date.valueOf());
|
||||||
|
ret.setDate(date.getDate() + days);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user