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