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