]> jfr.im git - irc/quakenet/newserv.git/blob - a4stats/a4stats_db.c
CHANSERV: better batcher error handling for expired accounts/accounts with no email.
[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 1, "
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 typedef struct user_update_info {
155 int stage;
156 char *update;
157 unsigned long channelid;
158 char *account;
159 unsigned long accountid;
160 } user_update_info;
161
162 static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) {
163 user_update_info *uui = uarg;
164
165 uui->stage++;
166
167 if (uui->stage == 1 || (result != NULL && uui->stage == 3))
168 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account);
169 else {
170 if (result == NULL || result->affected > 0 || uui->stage == 4) {
171 if (result == NULL || (result->affected == 0 && uui->stage == 4))
172 Error("a4stats", ERR_WARNING, "Unable to update user.");
173
174 free(uui->update);
175 free(uui->account);
176 free(uui);
177 goto a4_uuc_return;
178 }
179
180 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));
181 }
182
183 a4_uuc_return:
184 if (result)
185 result->clear(result);
186 }
187
188 static int a4stats_lua_update_user(lua_State *ps) {
189 const char *account;
190 unsigned long channelid, accountid;
191 char query[4096];
192 int first = 1;
193 user_update_info *uui;
194
195 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3))
196 LUA_RETURN(ps, LUA_FAIL);
197
198 channelid = lua_tonumber(ps, 1);
199 account = lua_tostring(ps, 2);
200 accountid = lua_tonumber(ps, 3);
201
202 strcpy(query, "UPDATE ? SET ");
203
204 lua_pushvalue(ps, 4);
205 lua_pushnil(ps);
206
207 while (lua_next(ps, -2)) {
208 const char *value = lua_tostring(ps, -1);
209
210 if (first)
211 first = 0;
212 else
213 strcat(query, ", ");
214
215 strcat(query, value);
216
217 lua_pop(ps, 1);
218 }
219
220 lua_pop(ps, 1);
221
222 strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
223
224 uui = malloc(sizeof(*uui));
225 uui->stage = 0;
226 uui->update = strdup(query);
227 uui->channelid = channelid;
228 uui->account = strdup(account);
229 uui->accountid = accountid;
230
231 a4stats_update_user_cb(NULL, uui);
232
233 LUA_RETURN(ps, LUA_OK);
234 }
235
236 typedef struct relation_update_info {
237 int stage;
238 unsigned long channelid;
239 char *first;
240 unsigned long firstid;
241 char *second;
242 unsigned long secondid;
243 } relation_update_info;
244
245 static void a4stats_update_relation_cb(const struct DBAPIResult *result, void *uarg) {
246 relation_update_info *rui = uarg;
247
248 rui->stage++;
249
250 if (rui->stage == 1) {
251 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "UPDATE ? SET score = score + 1, seen = ? "
252 "WHERE channelid = ? AND first = ? AND firstid = ? AND second = ? AND secondid = ?",
253 "TtUsUsU", "relations", time(NULL), rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid);
254 goto a4_urc_return;
255 } else if (rui->stage == 2 && result && result->affected == 0) {
256 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "INSERT INTO ? (channelid, first, firstid, second, secondid, seen) VALUES (?, ?, ?, ?, ?, ?)",
257 "TUsUsUt", "relations", rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid, time(NULL));
258 goto a4_urc_return;
259 }
260
261 if (!result || result->affected == 0)
262 Error("a4stats", ERR_WARNING, "Unable to update relation.");
263
264 free(rui->first);
265 free(rui->second);
266 free(rui);
267
268 a4_urc_return:
269 if (result)
270 result->clear(result);
271 }
272
273 static int a4stats_lua_update_relation(lua_State *ps) {
274 const char *user1, *user2;
275 unsigned long channelid, user1id, user2id;
276 relation_update_info *rui;
277
278 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4) || !lua_isnumber(ps, 5))
279 LUA_RETURN(ps, LUA_FAIL);
280
281 channelid = lua_tonumber(ps, 1);
282 user1 = lua_tostring(ps, 2);
283 user1id = lua_tonumber(ps, 3);
284 user2 = lua_tostring(ps, 4);
285 user2id = lua_tonumber(ps, 5);
286
287 rui = malloc(sizeof(*rui));
288 rui->stage = 0;
289 rui->channelid = channelid;
290
291 if (user1id < user2id || (user1id == user2id && strcmp(user1, user2) <= 0)) {
292 rui->first = strdup(user1);
293 rui->firstid = user1id;
294 rui->second = strdup(user2);
295 rui->secondid = user2id;
296 } else {
297 rui->first = strdup(user2);
298 rui->firstid = user2id;
299 rui->second = strdup(user1);
300 rui->secondid = user1id;
301 }
302
303 a4stats_update_relation_cb(NULL, rui);
304
305 LUA_RETURN(ps, LUA_OK);
306 }
307
308 static int a4stats_lua_add_line(lua_State *ps) {
309 char query[256];
310 const char *channel;
311 int hour;
312
313 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
314 LUA_RETURN(ps, LUA_FAIL);
315
316 channel = lua_tostring(ps, 1);
317 hour = lua_tonumber(ps, 2);
318
319 snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour, hour);
320
321 a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel);
322
323 LUA_RETURN(ps, LUA_OK);
324 }
325
326 static struct {
327 time_t start;
328 unsigned long pending;
329 unsigned long topicskicks;
330 unsigned long disabled;
331 unsigned long deleted;
332 } cleanupdata;
333
334 static void a4stats_cleanupdb_got_result(void) {
335 if (!--cleanupdata.pending) {
336 controlwall(NO_OPER, NL_CLEANUP, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.",
337 cleanupdata.topicskicks, cleanupdata.disabled, cleanupdata.deleted);
338 cleanupdata.start = 0;
339 }
340 }
341
342 static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult *result, void *arg) {
343 unsigned long *counter = arg;
344
345 if (result) {
346 *counter += result->affected;
347 result->clear(result);
348 }
349
350 a4stats_cleanupdb_got_result();
351 }
352
353 static void a4stats_cleanupdb_cb_active(const struct DBAPIResult *result, void *null) {
354 unsigned long channelid;
355 time_t seen;
356
357 if (!result)
358 return;
359
360 if (result->success) {
361 while (result->next(result)) {
362 channelid = strtoul(result->get(result, 0), NULL, 10);
363 seen = (time_t)strtoul(result->get(result, 2), NULL, 10);
364 /* use channel enabling timestamp if there was never any event */
365 if (!seen)
366 seen = (time_t)strtoul(result->get(result, 1), NULL, 10);
367
368 if (seen < cleanupdata.start - CLEANUP_INACTIVE_DAYS * 86400) {
369 /* disable inactive channels */
370 cleanupdata.pending++;
371 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.disabled,
372 "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1",
373 "TtU", "channels", cleanupdata.start, channelid);
374 } else {
375 /* cleanup old kicks/topics */
376 cleanupdata.pending++;
377 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
378 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
379 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
380 "TUTUU", "kicks", channelid, "kicks", channelid, (unsigned long)CLEANUP_KEEP);
381 cleanupdata.pending++;
382 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
383 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
384 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
385 "TUTUU", "topics", channelid, "topics", channelid, (unsigned long)CLEANUP_KEEP);
386 }
387 }
388 }
389 result->clear(result);
390 }
391
392 static void a4stats_cleanupdb(void *null) {
393 controlwall(NO_OPER, NL_CLEANUP, "Starting a4stats_db cleanup.");
394
395 if (cleanupdata.start != 0) {
396 controlwall(NO_OPER, NL_CLEANUP, "a4stats cleanup already in progress.");
397 return;
398 }
399
400 cleanupdata.start = time(NULL);
401 cleanupdata.pending = 0;
402 cleanupdata.topicskicks = 0;
403 cleanupdata.disabled = 0;
404 cleanupdata.deleted = 0;
405
406 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_active, NULL,
407 "SELECT id, timestamp, MAX(users.seen) FROM ? LEFT JOIN ? AS users ON id = users.channelid WHERE active = 1 GROUP BY id",
408 "TT", "channels", "users");
409 cleanupdata.pending++;
410 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.deleted,
411 "DELETE FROM ? WHERE active = 0 AND deleted < ?", "Tt", "channels", (time_t)(cleanupdata.start - CLEANUP_DELETE_DAYS * 84600));
412 }
413
414 static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) {
415 db_callback_info *dci = uarg;
416 unsigned long channelid;
417 int active;
418 char *channel;
419
420 if (result) {
421 if (result->success) {
422 while (result->next(result)) {
423 channelid = strtoul(result->get(result, 0), NULL, 10);
424 channel = result->get(result, 1);
425 active = atoi(result->get(result, 2));
426
427 if (dci->interp)
428 lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index);
429 }
430 }
431
432 result->clear(result);
433 }
434
435 if (dci->interp)
436 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
437
438 a4stats_delete_dci(dci);
439 }
440
441 static int a4stats_lua_fetch_channels(lua_State *ps) {
442 const char *callback;
443 db_callback_info *dci;
444
445 if (!lua_isstring(ps, 1))
446 LUA_RETURN(ps, LUA_FAIL);
447
448 callback = lua_tostring(ps, 1);
449
450 dci = malloc(sizeof(*dci));
451 dci->interp = ps;
452
453 strncpy(dci->callback, callback, sizeof(dci->callback));
454 dci->callback[sizeof(dci->callback) - 1] = '\0';
455
456 lua_pushvalue(ps, 2);
457 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
458
459 a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels");
460
461 LUA_RETURN(ps, LUA_OK);
462 }
463
464 static int a4stats_lua_enable_channel(lua_State *ps) {
465 if (!lua_isstring(ps, 1))
466 LUA_RETURN(ps, LUA_FAIL);
467
468 a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL));
469 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps, 1));
470
471 LUA_RETURN(ps, LUA_OK);
472 }
473
474 static int a4stats_lua_disable_channel(lua_State *ps) {
475 if (!lua_isstring(ps, 1))
476 LUA_RETURN(ps, LUA_FAIL);
477
478 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL), lua_tostring(ps, 1));
479
480 LUA_RETURN(ps, LUA_OK);
481 }
482
483 static int a4stats_lua_set_privacy(lua_State *ps) {
484 const char *channel;
485 unsigned long privacy;
486
487 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
488 LUA_RETURN(ps, LUA_FAIL);
489
490 channel = lua_tostring(ps, 1);
491 privacy = lua_tonumber(ps, 2);
492
493 a4statsdb->squery(a4statsdb, "UPDATE ? SET privacy = ? WHERE name = ?", "TUs", "channels", privacy, channel);
494 LUA_RETURN(ps, LUA_OK);
495 }
496
497 static int a4stats_lua_add_kick(lua_State *ps) {
498 unsigned long channelid, kickerid, victimid;
499 const char *kicker, *victim, *reason;
500
501 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))
502 LUA_RETURN(ps, LUA_FAIL);
503
504 channelid = lua_tonumber(ps, 1);
505 kicker = lua_tostring(ps, 2);
506 kickerid = lua_tonumber(ps, 3);
507 victim = lua_tostring(ps, 4);
508 victimid = lua_tonumber(ps, 5);
509 reason = lua_tostring(ps, 6);
510
511 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, kicker, kickerid, victim, victimid, timestamp, reason) VALUES (?, ?, ?, ?, ?, ?, ?)", "TUsUsUts",
512 "kicks", channelid, kicker, kickerid, victim, victimid, time(NULL), reason);
513
514 LUA_RETURN(ps, LUA_OK);
515 }
516
517 static int a4stats_lua_add_topic(lua_State *ps) {
518 unsigned long channelid, setbyid;
519 const char *topic, *setby;
520
521 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4))
522 LUA_RETURN(ps, LUA_FAIL);
523
524 channelid = lua_tonumber(ps, 1);
525 topic = lua_tostring(ps, 2);
526 setby = lua_tostring(ps, 3);
527 setbyid = lua_tonumber(ps, 4);
528
529 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
530 "topics", channelid, topic, time(NULL), setby, setbyid);
531
532 LUA_RETURN(ps, LUA_OK);
533 }
534
535 static int a4stats_lua_db_begin(lua_State *ps) {
536 a4statsdb->query(a4statsdb, NULL, NULL, "BEGIN TRANSACTION", "");
537 LUA_RETURN(ps, LUA_OK);
538 }
539
540 static int a4stats_lua_db_commit(lua_State *ps) {
541 a4statsdb->query(a4statsdb, NULL, NULL, "COMMIT TRANSACTION", "");
542 LUA_RETURN(ps, LUA_OK);
543 }
544
545 static void a4stats_hook_loadscript(int hooknum, void *arg) {
546 void **args = arg;
547 lua_State *l = args[1];
548
549 lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel);
550 lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel);
551 lua_register(l, "a4_set_privacy", a4stats_lua_set_privacy);
552 lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels);
553 lua_register(l, "a4_add_kick", a4stats_lua_add_kick);
554 lua_register(l, "a4_add_topic", a4stats_lua_add_topic);
555 lua_register(l, "a4_add_line", a4stats_lua_add_line);
556 lua_register(l, "a4_update_user", a4stats_lua_update_user);
557 lua_register(l, "a4_update_relation", a4stats_lua_update_relation);
558 lua_register(l, "a4_escape_string", a4stats_lua_escape_string);
559 lua_register(l, "a4_db_begin", a4stats_lua_db_begin);
560 lua_register(l, "a4_db_commit", a4stats_lua_db_commit);
561 }
562
563 #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
564
565 static void a4stats_hook_unloadscript(int hooknum, void *arg) {
566 db_callback_info **pnext, *dci;
567 lua_State *l = arg;
568
569 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
570 dci = *pnext;
571 if (dci->interp == l) {
572 *pnext = dci->next;
573 free(dci);
574 }
575 }
576 }
577
578 void _init(void) {
579 lua_list *l;
580 void *args[2];
581
582 a4stats_connectdb();
583
584 registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
585 registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
586 schedulerecurring(time(NULL), 0, CLEANUP_INTERVAL, a4stats_cleanupdb, NULL);
587
588 args[0] = NULL;
589 for (l = lua_head; l;l = l->next) {
590 args[1] = l->l;
591 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args);
592 }
593 }
594
595 void _fini(void) {
596 lua_list *l;
597
598 deleteschedule(NULL, a4stats_cleanupdb, NULL);
599 a4stats_closedb();
600
601 for (l = lua_head; l;l = l->next) {
602 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT, l->l);
603
604 lua_unregister(l->l, "a4_enable_channel");
605 lua_unregister(l->l, "a4_disable_channel");
606 lua_unregister(l->l, "a4_set_privacy");
607 lua_unregister(l->l, "a4_fetch_channels");
608 lua_unregister(l->l, "a4_add_kick");
609 lua_unregister(l->l, "a4_add_topic");
610 lua_unregister(l->l, "a4_add_line");
611 lua_unregister(l->l, "a4_fetch_user");
612 lua_unregister(l->l, "a4_update_user");
613 lua_unregister(l->l, "a4_update_relation");
614 lua_unregister(l->l, "a4_escape_string");
615 lua_unregister(l->l, "a4_db_begin");
616 lua_unregister(l->l, "a4_db_commit");
617 }
618
619 deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
620 deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
621 }
622