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"
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 */
21 static int a4stats_connectdb(void) {
23 a4statsdb
= dbapi2open("pqsql", "a4stats");
25 Error("a4stats", ERR_WARNING
, "Unable to connect to db -- not loaded.");
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");
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");
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");
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");
48 a4statsdb
->squery(a4statsdb
, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics");
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");
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");
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");
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");
77 static void a4stats_closedb(void) {
81 a4statsdb
->close(a4statsdb
);
85 static int a4stats_lua_escape_string(lua_State
*ps
) {
90 if (!lua_isstring(ps
, 1))
91 LUA_RETURN(ps
, LUA_FAIL
);
93 input
= lua_tostring(ps
, 1);
96 buf
= malloc(len
* 2 + 1);
99 LUA_RETURN(ps
, LUA_FAIL
);
101 a4statsdb
->escapestring(a4statsdb
, buf
, input
, len
);
103 buf2
= malloc(len
* 4 + 1);
107 LUA_RETURN(ps
, LUA_FAIL
);
112 for (i
= 0; buf
[i
]; i
++) {
118 buf2
[i
+ o
] = buf
[i
];
124 lua_pushstring(ps
, buf2
);
131 typedef struct db_callback_info
{
132 struct db_callback_info
*next
;
139 static db_callback_info
*dci_head
;
141 static void a4stats_delete_dci(db_callback_info
*dci
) {
142 db_callback_info
**pnext
;
144 for (pnext
= &dci_head
; *pnext
; pnext
= &((*pnext
)->next
)) {
154 typedef struct user_update_info
{
157 unsigned long channelid
;
159 unsigned long accountid
;
162 static void a4stats_update_user_cb(const struct DBAPIResult
*result
, void *uarg
) {
163 user_update_info
*uui
= uarg
;
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
);
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.");
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
));
185 result
->clear(result
);
188 static int a4stats_lua_update_user(lua_State
*ps
) {
190 unsigned long channelid
, accountid
;
193 user_update_info
*uui
;
195 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3))
196 LUA_RETURN(ps
, LUA_FAIL
);
198 channelid
= lua_tonumber(ps
, 1);
199 account
= lua_tostring(ps
, 2);
200 accountid
= lua_tonumber(ps
, 3);
202 strcpy(query
, "UPDATE ? SET ");
204 lua_pushvalue(ps
, 4);
207 while (lua_next(ps
, -2)) {
208 const char *value
= lua_tostring(ps
, -1);
215 strcat(query
, value
);
222 strcat(query
, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
224 uui
= malloc(sizeof(*uui
));
226 uui
->update
= strdup(query
);
227 uui
->channelid
= channelid
;
228 uui
->account
= strdup(account
);
229 uui
->accountid
= accountid
;
231 a4stats_update_user_cb(NULL
, uui
);
233 LUA_RETURN(ps
, LUA_OK
);
236 typedef struct relation_update_info
{
238 unsigned long channelid
;
240 unsigned long firstid
;
242 unsigned long secondid
;
243 } relation_update_info
;
245 static void a4stats_update_relation_cb(const struct DBAPIResult
*result
, void *uarg
) {
246 relation_update_info
*rui
= uarg
;
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
);
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
));
261 if (!result
|| result
->affected
== 0)
262 Error("a4stats", ERR_WARNING
, "Unable to update relation.");
270 result
->clear(result
);
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
;
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
);
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);
287 rui
= malloc(sizeof(*rui
));
289 rui
->channelid
= channelid
;
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
;
297 rui
->first
= strdup(user2
);
298 rui
->firstid
= user2id
;
299 rui
->second
= strdup(user1
);
300 rui
->secondid
= user1id
;
303 a4stats_update_relation_cb(NULL
, rui
);
305 LUA_RETURN(ps
, LUA_OK
);
308 static int a4stats_lua_add_line(lua_State
*ps
) {
313 if (!lua_isstring(ps
, 1) || !lua_isnumber(ps
, 2))
314 LUA_RETURN(ps
, LUA_FAIL
);
316 channel
= lua_tostring(ps
, 1);
317 hour
= lua_tonumber(ps
, 2);
319 snprintf(query
, sizeof(query
), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour
, hour
);
321 a4statsdb
->squery(a4statsdb
, query
, "Ts", "channels", channel
);
323 LUA_RETURN(ps
, LUA_OK
);
328 unsigned long pending
;
329 unsigned long topicskicks
;
330 unsigned long disabled
;
331 unsigned long deleted
;
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;
342 static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult
*result
, void *arg
) {
343 unsigned long *counter
= arg
;
346 *counter
+= result
->affected
;
347 result
->clear(result
);
350 a4stats_cleanupdb_got_result();
353 static void a4stats_cleanupdb_cb_active(const struct DBAPIResult
*result
, void *null
) {
354 unsigned long channelid
;
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 */
366 seen
= (time_t)strtoul(result
->get(result
, 1), NULL
, 10);
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
);
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
);
389 result
->clear(result
);
392 static void a4stats_cleanupdb(void *null
) {
393 controlwall(NO_OPER
, NL_CLEANUP
, "Starting a4stats_db cleanup.");
395 if (cleanupdata
.start
!= 0) {
396 controlwall(NO_OPER
, NL_CLEANUP
, "a4stats cleanup already in progress.");
400 cleanupdata
.start
= time(NULL
);
401 cleanupdata
.pending
= 0;
402 cleanupdata
.topicskicks
= 0;
403 cleanupdata
.disabled
= 0;
404 cleanupdata
.deleted
= 0;
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));
414 static void a4stats_fetch_channels_cb(const struct DBAPIResult
*result
, void *uarg
) {
415 db_callback_info
*dci
= uarg
;
416 unsigned long channelid
;
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));
428 lua_vpcall(dci
->interp
, dci
->callback
, "lsiR", channelid
, channel
, active
, dci
->uarg_index
);
432 result
->clear(result
);
436 luaL_unref(dci
->interp
, LUA_REGISTRYINDEX
, dci
->uarg_index
);
438 a4stats_delete_dci(dci
);
441 static int a4stats_lua_fetch_channels(lua_State
*ps
) {
442 const char *callback
;
443 db_callback_info
*dci
;
445 if (!lua_isstring(ps
, 1))
446 LUA_RETURN(ps
, LUA_FAIL
);
448 callback
= lua_tostring(ps
, 1);
450 dci
= malloc(sizeof(*dci
));
453 strncpy(dci
->callback
, callback
, sizeof(dci
->callback
));
454 dci
->callback
[sizeof(dci
->callback
) - 1] = '\0';
456 lua_pushvalue(ps
, 2);
457 dci
->uarg_index
= luaL_ref(ps
, LUA_REGISTRYINDEX
);
459 a4statsdb
->query(a4statsdb
, a4stats_fetch_channels_cb
, dci
, "SELECT id, name, active FROM ?", "T", "channels");
461 LUA_RETURN(ps
, LUA_OK
);
464 static int a4stats_lua_enable_channel(lua_State
*ps
) {
465 if (!lua_isstring(ps
, 1))
466 LUA_RETURN(ps
, LUA_FAIL
);
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));
471 LUA_RETURN(ps
, LUA_OK
);
474 static int a4stats_lua_disable_channel(lua_State
*ps
) {
475 if (!lua_isstring(ps
, 1))
476 LUA_RETURN(ps
, LUA_FAIL
);
478 a4statsdb
->squery(a4statsdb
, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL
), lua_tostring(ps
, 1));
480 LUA_RETURN(ps
, LUA_OK
);
483 static int a4stats_lua_set_privacy(lua_State
*ps
) {
485 unsigned long privacy
;
487 if (!lua_isstring(ps
, 1) || !lua_isnumber(ps
, 2))
488 LUA_RETURN(ps
, LUA_FAIL
);
490 channel
= lua_tostring(ps
, 1);
491 privacy
= lua_tonumber(ps
, 2);
493 a4statsdb
->squery(a4statsdb
, "UPDATE ? SET privacy = ? WHERE name = ?", "TUs", "channels", privacy
, channel
);
494 LUA_RETURN(ps
, LUA_OK
);
497 static int a4stats_lua_add_kick(lua_State
*ps
) {
498 unsigned long channelid
, kickerid
, victimid
;
499 const char *kicker
, *victim
, *reason
;
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
);
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);
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
);
514 LUA_RETURN(ps
, LUA_OK
);
517 static int a4stats_lua_add_topic(lua_State
*ps
) {
518 unsigned long channelid
, setbyid
;
519 const char *topic
, *setby
;
521 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isstring(ps
, 3) || !lua_isnumber(ps
, 4))
522 LUA_RETURN(ps
, LUA_FAIL
);
524 channelid
= lua_tonumber(ps
, 1);
525 topic
= lua_tostring(ps
, 2);
526 setby
= lua_tostring(ps
, 3);
527 setbyid
= lua_tonumber(ps
, 4);
529 a4statsdb
->squery(a4statsdb
, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
530 "topics", channelid
, topic
, time(NULL
), setby
, setbyid
);
532 LUA_RETURN(ps
, LUA_OK
);
535 static int a4stats_lua_db_begin(lua_State
*ps
) {
536 a4statsdb
->query(a4statsdb
, NULL
, NULL
, "BEGIN TRANSACTION", "");
537 LUA_RETURN(ps
, LUA_OK
);
540 static int a4stats_lua_db_commit(lua_State
*ps
) {
541 a4statsdb
->query(a4statsdb
, NULL
, NULL
, "COMMIT TRANSACTION", "");
542 LUA_RETURN(ps
, LUA_OK
);
545 static void a4stats_hook_loadscript(int hooknum
, void *arg
) {
547 lua_State
*l
= args
[1];
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
);
563 #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
565 static void a4stats_hook_unloadscript(int hooknum
, void *arg
) {
566 db_callback_info
**pnext
, *dci
;
569 for (pnext
= &dci_head
; *pnext
; pnext
= &((*pnext
)->next
)) {
571 if (dci
->interp
== l
) {
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
);
589 for (l
= lua_head
; l
;l
= l
->next
) {
591 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT
, args
);
598 deleteschedule(NULL
, a4stats_cleanupdb
, NULL
);
601 for (l
= lua_head
; l
;l
= l
->next
) {
602 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT
, l
->l
);
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");
619 deregisterhook(HOOK_LUA_LOADSCRIPT
, a4stats_hook_loadscript
);
620 deregisterhook(HOOK_LUA_UNLOADSCRIPT
, a4stats_hook_unloadscript
);