]> jfr.im git - irc/quakenet/newserv.git/blame - a4stats/a4stats_db.c
CHANSERV: channel and user cleanup now 240/120 days
[irc/quakenet/newserv.git] / a4stats / a4stats_db.c
CommitLineData
d5c004ba
GB
1#include <stdio.h>
2#include <stdarg.h>
3#include "../lib/version.h"
4#include "../dbapi2/dbapi2.h"
5#include "../core/error.h"
6#include "../core/hooks.h"
7#include "../core/schedule.h"
8#include "../control/control.h"
9#include "../irc/irc.h"
10#include "../lua/lua.h"
11
5905e45f 12#define CLEANUP_KEEP 10 /* keep this many topics and kicks per channel around */
ef90464f 13#define CLEANUP_INTERVAL 86400 /* db cleanup interval (in seconds) */
82645134 14#define CLEANUP_INACTIVE_DAYS 30 /* disable channels where nothing happened for this many days */
5af6ffcf 15#define CLEANUP_DELETE_DAYS 5 /* delete data for channels that have been disabled for this many days */
5905e45f 16
a0b4a0cf
TS
17#define A4STATS_DB_TOLOWER(x) "translate(lower(" x "), E'[]\\\\~', '{}|^')"
18#define A4STATS_DB_EQ_NOCASE(x, y) A4STATS_DB_TOLOWER(x) " = " A4STATS_DB_TOLOWER(y)
19
d5c004ba
GB
20MODULE_VERSION("");
21
22DBAPIConn *a4statsdb;
23
24static int a4stats_connectdb(void) {
25 if(!a4statsdb) {
cf5ac373 26 a4statsdb = dbapi2open("pqsql", "a4stats");
d5c004ba
GB
27 if(!a4statsdb) {
28 Error("a4stats", ERR_WARNING, "Unable to connect to db -- not loaded.");
29 return 0;
30 }
31 }
32
33 a4statsdb->createtable(a4statsdb, NULL, NULL,
53c0b4a2 34 "CREATE TABLE ? (id SERIAL PRIMARY KEY, name VARCHAR(256) UNIQUE, timestamp INT DEFAULT 0, active INT DEFAULT 1, deleted INT DEFAULT 0, privacy INT DEFAULT 1, "
92898969
GB
35 "h0 INT DEFAULT 0, h1 INT DEFAULT 0, h2 INT DEFAULT 0, h3 INT DEFAULT 0, h4 INT DEFAULT 0, h5 INT DEFAULT 0, "
36 "h6 INT DEFAULT 0, h7 INT DEFAULT 0, h8 INT DEFAULT 0, h9 INT DEFAULT 0, h10 INT DEFAULT 0, h11 INT DEFAULT 0, "
37 "h12 INT DEFAULT 0, h13 INT DEFAULT 0, h14 INT DEFAULT 0, h15 INT DEFAULT 0, h16 INT DEFAULT 0, h17 INT DEFAULT 0, "
38 "h18 INT DEFAULT 0, h19 INT DEFAULT 0, h20 INT DEFAULT 0, h21 INT DEFAULT 0, h22 INT DEFAULT 0, h23 INT DEFAULT 0)", "T", "channels");
d5c004ba 39
f79faea1 40 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
41 "CREATE TABLE ? (channelid INT, kicker VARCHAR(128), kickerid INT, victim VARCHAR(128), victimid INT, timestamp INT, reason VARCHAR(256),"
42 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "kicks", "channels");
f79faea1 43
cf5ac373
GB
44 a4statsdb->squery(a4statsdb, "CREATE INDEX kicks_channelid_index ON ? (channelid)", "T", "kicks");
45 a4statsdb->squery(a4statsdb, "CREATE INDEX kicks_timestamp_index ON ? (timestamp)", "T", "kicks");
d5c004ba
GB
46
47 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
48 "CREATE TABLE ? (channelid INT, setby VARCHAR(128), setbyid INT, timestamp INT, topic VARCHAR(512),"
49 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "topics", "channels");
d5c004ba 50
cf5ac373 51 a4statsdb->squery(a4statsdb, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics");
d5c004ba
GB
52
53 a4statsdb->createtable(a4statsdb, NULL, NULL,
cf5ac373 54 "CREATE TABLE ? (channelid INT, account VARCHAR(128), accountid INT, seen INT DEFAULT 0, rating INT DEFAULT 0, lines INT DEFAULT 0, chars INT DEFAULT 0, words INT DEFAULT 0, "
f79faea1
GB
55 "h0 INT DEFAULT 0, h1 INT DEFAULT 0, h2 INT DEFAULT 0, h3 INT DEFAULT 0, h4 INT DEFAULT 0, h5 INT DEFAULT 0, "
56 "h6 INT DEFAULT 0, h7 INT DEFAULT 0, h8 INT DEFAULT 0, h9 INT DEFAULT 0, h10 INT DEFAULT 0, h11 INT DEFAULT 0, "
57 "h12 INT DEFAULT 0, h13 INT DEFAULT 0, h14 INT DEFAULT 0, h15 INT DEFAULT 0, h16 INT DEFAULT 0, h17 INT DEFAULT 0, "
58 "h18 INT DEFAULT 0, h19 INT DEFAULT 0, h20 INT DEFAULT 0, h21 INT DEFAULT 0, h22 INT DEFAULT 0, h23 INT DEFAULT 0, "
e0311e96 59 "last VARCHAR(512), quote VARCHAR(512), quotereset INT DEFAULT 0, mood_happy INT DEFAULT 0, mood_sad INT DEFAULT 0, questions INT DEFAULT 0, yelling INT DEFAULT 0, caps INT DEFAULT 0, "
d5c004ba 60 "slaps INT DEFAULT 0, slapped INT DEFAULT 0, highlights INT DEFAULT 0, kicks INT DEFAULT 0, kicked INT DEFAULT 0, ops INT DEFAULT 0, deops INT DEFAULT 0, actions INT DEFAULT 0, skitzo INT DEFAULT 0, foul INT DEFAULT 0, "
5af6ffcf 61 "firstseen INT DEFAULT 0, curnick VARCHAR(16), FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "users", "channels");
d5c004ba 62
cf5ac373
GB
63 a4statsdb->squery(a4statsdb, "CREATE INDEX users_account_index ON ? (account)", "T", "users");
64 a4statsdb->squery(a4statsdb, "CREATE INDEX users_accountid_index ON ? (accountid)", "T", "users");
65 a4statsdb->squery(a4statsdb, "CREATE INDEX users_channelid_index ON ? (channelid)", "T", "users");
66 a4statsdb->squery(a4statsdb, "CREATE UNIQUE INDEX users_channelid_account_accountid_index ON ? (channelid, account, accountid)", "T", "users");
67 a4statsdb->squery(a4statsdb, "CREATE INDEX users_channelid_lines_index ON ? (channelid, lines)", "T", "users");
d5c004ba 68
28d6a377 69 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
70 "CREATE TABLE ? (channelid INT, first VARCHAR(128), firstid INT, second VARCHAR(128), secondid INT, seen INT, score INT DEFAULT 1,"
71 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "relations", "channels");
28d6a377 72
8d6e0508
GB
73 a4statsdb->squery(a4statsdb, "CREATE INDEX relations_channelid_index ON ? (channelid)", "T", "relations");
74 a4statsdb->squery(a4statsdb, "CREATE INDEX relations_score_index ON ? (score)", "T", "relations");
75
76
d5c004ba
GB
77 return 1;
78}
79
80static void a4stats_closedb(void) {
81 if(!a4statsdb)
82 return;
83
84 a4statsdb->close(a4statsdb);
85 a4statsdb = NULL;
86}
87
88static int a4stats_lua_escape_string(lua_State *ps) {
89 const char *input;
90 char *buf, *buf2;
91 size_t len, i, o;
92
93 if (!lua_isstring(ps, 1))
94 LUA_RETURN(ps, LUA_FAIL);
95
96 input = lua_tostring(ps, 1);
97 len = strlen(input);
98
99 buf = malloc(len * 2 + 1);
100
101 if (!buf)
102 LUA_RETURN(ps, LUA_FAIL);
103
104 a4statsdb->escapestring(a4statsdb, buf, input, len);
105
106 buf2 = malloc(len * 4 + 1);
107
108 if (!buf2) {
109 free(buf);
110 LUA_RETURN(ps, LUA_FAIL);
111 }
112
113 /* escape "?" */
114 o = 0;
115 for (i = 0; buf[i]; i++) {
116 if (buf[i] == '?') {
117 buf2[i + o] = '\\';
118 o++;
119 }
120
121 buf2[i + o] = buf[i];
122 }
123 buf2[i + o] = '\0';
124
125 free(buf);
126
127 lua_pushstring(ps, buf2);
128
129 free(buf2);
130
131 return 1;
132}
133
f79faea1
GB
134typedef struct db_callback_info {
135 struct db_callback_info *next;
d5c004ba
GB
136
137 lua_State *interp;
138 char callback[64];
139 int uarg_index;
f79faea1
GB
140} db_callback_info;
141
142static db_callback_info *dci_head;
143
144static void a4stats_delete_dci(db_callback_info *dci) {
5905e45f 145 db_callback_info **pnext;
d5c004ba 146
f79faea1
GB
147 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
148 if (*pnext == dci) {
149 *pnext = dci->next;
150 break;
151 }
152 }
153
154 free(dci);
155}
d5c004ba 156
d5c004ba
GB
157typedef struct user_update_info {
158 int stage;
159 char *update;
f79faea1 160 unsigned long channelid;
d5c004ba
GB
161 char *account;
162 unsigned long accountid;
163} user_update_info;
164
165static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) {
166 user_update_info *uui = uarg;
167
168 uui->stage++;
169
e6a4eec1 170 if (uui->stage == 1 || (result != NULL && uui->stage == 3))
f79faea1 171 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account);
d5c004ba 172 else {
e6a4eec1 173 if (result == NULL || result->affected > 0 || uui->stage == 4) {
424b0e83 174 if (result == NULL || (result->affected == 0 && uui->stage == 4))
d5c004ba
GB
175 Error("a4stats", ERR_WARNING, "Unable to update user.");
176
177 free(uui->update);
d5c004ba
GB
178 free(uui->account);
179 free(uui);
316ff6c6 180 goto a4_uuc_return;
d5c004ba
GB
181 }
182
f79faea1 183 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, "INSERT INTO ? (channelid, account, accountid, firstseen) VALUES (?, ?, ?, ?)", "TUsUt", "users", uui->channelid, uui->account, uui->accountid, time(NULL));
d5c004ba 184 }
316ff6c6
TS
185
186a4_uuc_return:
187 if (result)
188 result->clear(result);
d5c004ba
GB
189}
190
191static int a4stats_lua_update_user(lua_State *ps) {
f79faea1
GB
192 const char *account;
193 unsigned long channelid, accountid;
d5c004ba
GB
194 char query[4096];
195 int first = 1;
196 user_update_info *uui;
197
f79faea1 198 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3))
d5c004ba
GB
199 LUA_RETURN(ps, LUA_FAIL);
200
f79faea1 201 channelid = lua_tonumber(ps, 1);
d5c004ba
GB
202 account = lua_tostring(ps, 2);
203 accountid = lua_tonumber(ps, 3);
204
205 strcpy(query, "UPDATE ? SET ");
206
207 lua_pushvalue(ps, 4);
208 lua_pushnil(ps);
209
210 while (lua_next(ps, -2)) {
211 const char *value = lua_tostring(ps, -1);
212
213 if (first)
214 first = 0;
215 else
216 strcat(query, ", ");
217
218 strcat(query, value);
219
220 lua_pop(ps, 1);
221 }
222
223 lua_pop(ps, 1);
224
f79faea1 225 strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
d5c004ba
GB
226
227 uui = malloc(sizeof(*uui));
228 uui->stage = 0;
229 uui->update = strdup(query);
f79faea1 230 uui->channelid = channelid;
d5c004ba
GB
231 uui->account = strdup(account);
232 uui->accountid = accountid;
233
234 a4stats_update_user_cb(NULL, uui);
235
236 LUA_RETURN(ps, LUA_OK);
237}
238
28d6a377
GB
239typedef struct relation_update_info {
240 int stage;
241 unsigned long channelid;
242 char *first;
243 unsigned long firstid;
244 char *second;
245 unsigned long secondid;
246} relation_update_info;
247
248static void a4stats_update_relation_cb(const struct DBAPIResult *result, void *uarg) {
249 relation_update_info *rui = uarg;
250
251 rui->stage++;
252
253 if (rui->stage == 1) {
254 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "UPDATE ? SET score = score + 1, seen = ? "
255 "WHERE channelid = ? AND first = ? AND firstid = ? AND second = ? AND secondid = ?",
256 "TtUsUsU", "relations", time(NULL), rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid);
316ff6c6 257 goto a4_urc_return;
28d6a377
GB
258 } else if (rui->stage == 2 && result && result->affected == 0) {
259 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "INSERT INTO ? (channelid, first, firstid, second, secondid, seen) VALUES (?, ?, ?, ?, ?, ?)",
260 "TUsUsUt", "relations", rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid, time(NULL));
316ff6c6 261 goto a4_urc_return;
28d6a377
GB
262 }
263
264 if (!result || result->affected == 0)
265 Error("a4stats", ERR_WARNING, "Unable to update relation.");
266
267 free(rui->first);
268 free(rui->second);
269 free(rui);
316ff6c6
TS
270
271a4_urc_return:
272 if (result)
273 result->clear(result);
28d6a377
GB
274}
275
276static int a4stats_lua_update_relation(lua_State *ps) {
277 const char *user1, *user2;
278 unsigned long channelid, user1id, user2id;
279 relation_update_info *rui;
280
281 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4) || !lua_isnumber(ps, 5))
282 LUA_RETURN(ps, LUA_FAIL);
283
284 channelid = lua_tonumber(ps, 1);
285 user1 = lua_tostring(ps, 2);
286 user1id = lua_tonumber(ps, 3);
287 user2 = lua_tostring(ps, 4);
288 user2id = lua_tonumber(ps, 5);
289
290 rui = malloc(sizeof(*rui));
291 rui->stage = 0;
292 rui->channelid = channelid;
293
294 if (user1id < user2id || (user1id == user2id && strcmp(user1, user2) <= 0)) {
295 rui->first = strdup(user1);
296 rui->firstid = user1id;
297 rui->second = strdup(user2);
298 rui->secondid = user2id;
299 } else {
300 rui->first = strdup(user2);
301 rui->firstid = user2id;
302 rui->second = strdup(user1);
303 rui->secondid = user1id;
304 }
305
306 a4stats_update_relation_cb(NULL, rui);
307
308 LUA_RETURN(ps, LUA_OK);
309}
310
92898969
GB
311static int a4stats_lua_add_line(lua_State *ps) {
312 char query[256];
313 const char *channel;
314 int hour;
315
316 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
317 LUA_RETURN(ps, LUA_FAIL);
318
319 channel = lua_tostring(ps, 1);
320 hour = lua_tonumber(ps, 2);
321
a0b4a0cf 322 snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), hour, hour);
92898969
GB
323
324 a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel);
325
326 LUA_RETURN(ps, LUA_OK);
327}
328
5af6ffcf
TS
329static struct {
330 time_t start;
82645134
TS
331 unsigned long pending;
332 unsigned long topicskicks;
333 unsigned long disabled;
5af6ffcf 334 unsigned long deleted;
82645134
TS
335} cleanupdata;
336
5af6ffcf
TS
337static void a4stats_cleanupdb_got_result(void) {
338 if (!--cleanupdata.pending) {
23c149d9 339 controlwall(NO_OPER, NL_CLEANUP, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.",
5af6ffcf
TS
340 cleanupdata.topicskicks, cleanupdata.disabled, cleanupdata.deleted);
341 cleanupdata.start = 0;
342 }
82645134
TS
343}
344
5af6ffcf
TS
345static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult *result, void *arg) {
346 unsigned long *counter = arg;
82645134 347
8a12ea21 348 if (result) {
5af6ffcf 349 *counter += result->affected;
8a12ea21
TS
350 result->clear(result);
351 }
82645134 352
5af6ffcf 353 a4stats_cleanupdb_got_result();
5905e45f
TS
354}
355
5af6ffcf 356static void a4stats_cleanupdb_cb_active(const struct DBAPIResult *result, void *null) {
82645134
TS
357 unsigned long channelid;
358 time_t seen;
8a12ea21
TS
359
360 if (!result)
361 return;
362
363 if (result->success) {
5905e45f
TS
364 while (result->next(result)) {
365 channelid = strtoul(result->get(result, 0), NULL, 10);
82645134
TS
366 seen = (time_t)strtoul(result->get(result, 2), NULL, 10);
367 /* use channel enabling timestamp if there was never any event */
368 if (!seen)
369 seen = (time_t)strtoul(result->get(result, 1), NULL, 10);
370
5af6ffcf
TS
371 if (seen < cleanupdata.start - CLEANUP_INACTIVE_DAYS * 86400) {
372 /* disable inactive channels */
373 cleanupdata.pending++;
374 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.disabled,
82645134 375 "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1",
5af6ffcf 376 "TtU", "channels", cleanupdata.start, channelid);
82645134
TS
377 } else {
378 /* cleanup old kicks/topics */
5af6ffcf
TS
379 cleanupdata.pending++;
380 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
82645134
TS
381 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
382 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
383 "TUTUU", "kicks", channelid, "kicks", channelid, (unsigned long)CLEANUP_KEEP);
5af6ffcf
TS
384 cleanupdata.pending++;
385 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
82645134
TS
386 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
387 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
388 "TUTUU", "topics", channelid, "topics", channelid, (unsigned long)CLEANUP_KEEP);
389 }
5905e45f
TS
390 }
391 }
8a12ea21 392 result->clear(result);
5905e45f
TS
393}
394
395static void a4stats_cleanupdb(void *null) {
5af6ffcf 396 controlwall(NO_OPER, NL_CLEANUP, "Starting a4stats_db cleanup.");
82645134 397
5af6ffcf
TS
398 if (cleanupdata.start != 0) {
399 controlwall(NO_OPER, NL_CLEANUP, "a4stats cleanup already in progress.");
400 return;
401 }
5905e45f 402
5af6ffcf
TS
403 cleanupdata.start = time(NULL);
404 cleanupdata.pending = 0;
405 cleanupdata.topicskicks = 0;
406 cleanupdata.disabled = 0;
407 cleanupdata.deleted = 0;
408
409 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_active, NULL,
82645134
TS
410 "SELECT id, timestamp, MAX(users.seen) FROM ? LEFT JOIN ? AS users ON id = users.channelid WHERE active = 1 GROUP BY id",
411 "TT", "channels", "users");
5af6ffcf
TS
412 cleanupdata.pending++;
413 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.deleted,
414 "DELETE FROM ? WHERE active = 0 AND deleted < ?", "Tt", "channels", (time_t)(cleanupdata.start - CLEANUP_DELETE_DAYS * 84600));
5905e45f
TS
415}
416
f79faea1
GB
417static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) {
418 db_callback_info *dci = uarg;
419 unsigned long channelid;
420 int active;
421 char *channel;
422
423 if (result) {
424 if (result->success) {
425 while (result->next(result)) {
426 channelid = strtoul(result->get(result, 0), NULL, 10);
427 channel = result->get(result, 1);
428 active = atoi(result->get(result, 2));
429
430 if (dci->interp)
cf5ac373 431 lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index);
f79faea1
GB
432 }
433 }
434
435 result->clear(result);
436 }
437
438 if (dci->interp)
439 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
440
441 a4stats_delete_dci(dci);
442}
443
444static int a4stats_lua_fetch_channels(lua_State *ps) {
445 const char *callback;
446 db_callback_info *dci;
447
448 if (!lua_isstring(ps, 1))
449 LUA_RETURN(ps, LUA_FAIL);
450
451 callback = lua_tostring(ps, 1);
452
453 dci = malloc(sizeof(*dci));
454 dci->interp = ps;
455
456 strncpy(dci->callback, callback, sizeof(dci->callback));
457 dci->callback[sizeof(dci->callback) - 1] = '\0';
458
459 lua_pushvalue(ps, 2);
460 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
461
462 a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels");
463
464 LUA_RETURN(ps, LUA_OK);
465}
466
5bf3ab9d 467static int a4stats_lua_enable_channel(lua_State *ps) {
f79faea1
GB
468 if (!lua_isstring(ps, 1))
469 LUA_RETURN(ps, LUA_FAIL);
470
899f2ee4 471 a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL));
a0b4a0cf 472 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "Ts", "channels", lua_tostring(ps, 1));
5bf3ab9d
GB
473
474 LUA_RETURN(ps, LUA_OK);
475}
476
477static int a4stats_lua_disable_channel(lua_State *ps) {
478 if (!lua_isstring(ps, 1))
479 LUA_RETURN(ps, LUA_FAIL);
480
a0b4a0cf 481 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "Tts", "channels", time(NULL), lua_tostring(ps, 1));
f79faea1
GB
482
483 LUA_RETURN(ps, LUA_OK);
484}
485
1b086b45
TS
486static int a4stats_lua_set_privacy(lua_State *ps) {
487 const char *channel;
488 unsigned long privacy;
489
490 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
491 LUA_RETURN(ps, LUA_FAIL);
492
493 channel = lua_tostring(ps, 1);
494 privacy = lua_tonumber(ps, 2);
495
a0b4a0cf 496 a4statsdb->squery(a4statsdb, "UPDATE ? SET privacy = ? WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "TUs", "channels", privacy, channel);
1b086b45
TS
497 LUA_RETURN(ps, LUA_OK);
498}
499
d5c004ba 500static int a4stats_lua_add_kick(lua_State *ps) {
f79faea1
GB
501 unsigned long channelid, kickerid, victimid;
502 const char *kicker, *victim, *reason;
503
504 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4) || !lua_isnumber(ps, 5) || !lua_isstring(ps, 6))
d5c004ba
GB
505 LUA_RETURN(ps, LUA_FAIL);
506
f79faea1
GB
507 channelid = lua_tonumber(ps, 1);
508 kicker = lua_tostring(ps, 2);
509 kickerid = lua_tonumber(ps, 3);
510 victim = lua_tostring(ps, 4);
511 victimid = lua_tonumber(ps, 5);
512 reason = lua_tostring(ps, 6);
513
514 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, kicker, kickerid, victim, victimid, timestamp, reason) VALUES (?, ?, ?, ?, ?, ?, ?)", "TUsUsUts",
515 "kicks", channelid, kicker, kickerid, victim, victimid, time(NULL), reason);
d5c004ba
GB
516
517 LUA_RETURN(ps, LUA_OK);
518}
519
520static int a4stats_lua_add_topic(lua_State *ps) {
f79faea1
GB
521 unsigned long channelid, setbyid;
522 const char *topic, *setby;
523
524 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4))
d5c004ba
GB
525 LUA_RETURN(ps, LUA_FAIL);
526
f79faea1
GB
527 channelid = lua_tonumber(ps, 1);
528 topic = lua_tostring(ps, 2);
529 setby = lua_tostring(ps, 3);
530 setbyid = lua_tonumber(ps, 4);
531
532 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
533 "topics", channelid, topic, time(NULL), setby, setbyid);
d5c004ba
GB
534
535 LUA_RETURN(ps, LUA_OK);
536}
537
65c41787
TS
538static int a4stats_lua_db_begin(lua_State *ps) {
539 a4statsdb->query(a4statsdb, NULL, NULL, "BEGIN TRANSACTION", "");
540 LUA_RETURN(ps, LUA_OK);
541}
542
543static int a4stats_lua_db_commit(lua_State *ps) {
544 a4statsdb->query(a4statsdb, NULL, NULL, "COMMIT TRANSACTION", "");
545 LUA_RETURN(ps, LUA_OK);
546}
547
d5c004ba
GB
548static void a4stats_hook_loadscript(int hooknum, void *arg) {
549 void **args = arg;
550 lua_State *l = args[1];
551
5bf3ab9d
GB
552 lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel);
553 lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel);
1b086b45 554 lua_register(l, "a4_set_privacy", a4stats_lua_set_privacy);
f79faea1 555 lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels);
d5c004ba
GB
556 lua_register(l, "a4_add_kick", a4stats_lua_add_kick);
557 lua_register(l, "a4_add_topic", a4stats_lua_add_topic);
92898969 558 lua_register(l, "a4_add_line", a4stats_lua_add_line);
d5c004ba 559 lua_register(l, "a4_update_user", a4stats_lua_update_user);
28d6a377 560 lua_register(l, "a4_update_relation", a4stats_lua_update_relation);
d5c004ba 561 lua_register(l, "a4_escape_string", a4stats_lua_escape_string);
65c41787
TS
562 lua_register(l, "a4_db_begin", a4stats_lua_db_begin);
563 lua_register(l, "a4_db_commit", a4stats_lua_db_commit);
d5c004ba
GB
564}
565
566#define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
567
568static void a4stats_hook_unloadscript(int hooknum, void *arg) {
f79faea1 569 db_callback_info **pnext, *dci;
899f2ee4 570 lua_State *l = arg;
d5c004ba 571
f79faea1
GB
572 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
573 dci = *pnext;
899f2ee4 574 if (dci->interp == l) {
f79faea1
GB
575 *pnext = dci->next;
576 free(dci);
d5c004ba
GB
577 }
578 }
d5c004ba
GB
579}
580
581void _init(void) {
582 lua_list *l;
583 void *args[2];
584
585 a4stats_connectdb();
586
587 registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
588 registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
5905e45f 589 schedulerecurring(time(NULL), 0, CLEANUP_INTERVAL, a4stats_cleanupdb, NULL);
d5c004ba
GB
590
591 args[0] = NULL;
592 for (l = lua_head; l;l = l->next) {
593 args[1] = l->l;
594 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args);
595 }
596}
597
598void _fini(void) {
f79faea1
GB
599 lua_list *l;
600
5905e45f 601 deleteschedule(NULL, a4stats_cleanupdb, NULL);
d5c004ba
GB
602 a4stats_closedb();
603
f79faea1 604 for (l = lua_head; l;l = l->next) {
064db467 605 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT, l->l);
899f2ee4
GB
606
607 lua_unregister(l->l, "a4_enable_channel");
608 lua_unregister(l->l, "a4_disable_channel");
1b086b45 609 lua_unregister(l->l, "a4_set_privacy");
899f2ee4
GB
610 lua_unregister(l->l, "a4_fetch_channels");
611 lua_unregister(l->l, "a4_add_kick");
612 lua_unregister(l->l, "a4_add_topic");
92898969 613 lua_unregister(l->l, "a4_add_line");
899f2ee4
GB
614 lua_unregister(l->l, "a4_fetch_user");
615 lua_unregister(l->l, "a4_update_user");
28d6a377 616 lua_unregister(l->l, "a4_update_relation");
899f2ee4 617 lua_unregister(l->l, "a4_escape_string");
65c41787
TS
618 lua_unregister(l->l, "a4_db_begin");
619 lua_unregister(l->l, "a4_db_commit");
f79faea1
GB
620 }
621
d5c004ba
GB
622 deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
623 deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
624}
625