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 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;
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;
166 result
->clear(result
);
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
);
174 a4stats_delete_dci(dci
);
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
;
182 if (!lua_islong(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3) || !lua_isstring(ps
, 4))
183 LUA_RETURN(ps
, LUA_FAIL
);
185 channelid
= lua_tonumber(ps
, 1);
186 account
= lua_tostring(ps
, 2);
187 accountid
= lua_tonumber(ps
, 3);
188 callback
= lua_tostring(ps
, 4);
190 dci
= malloc(sizeof(*dci
));
193 strncpy(dci
->callback
, callback
, sizeof(dci
->callback
));
194 dci
->callback
[sizeof(dci
->callback
) - 1] = '\0';
196 lua_pushvalue(ps
, 5);
197 dci
->uarg_index
= luaL_ref(ps
, LUA_REGISTRYINDEX
);
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
);
201 LUA_RETURN(ps
, LUA_OK
);
204 typedef struct user_update_info
{
207 unsigned long channelid
;
209 unsigned long accountid
;
212 static void a4stats_update_user_cb(const struct DBAPIResult
*result
, void *uarg
) {
213 user_update_info
*uui
= uarg
;
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
);
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.");
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
));
235 result
->clear(result
);
238 static int a4stats_lua_update_user(lua_State
*ps
) {
240 unsigned long channelid
, accountid
;
243 user_update_info
*uui
;
245 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3))
246 LUA_RETURN(ps
, LUA_FAIL
);
248 channelid
= lua_tonumber(ps
, 1);
249 account
= lua_tostring(ps
, 2);
250 accountid
= lua_tonumber(ps
, 3);
252 strcpy(query
, "UPDATE ? SET ");
254 lua_pushvalue(ps
, 4);
257 while (lua_next(ps
, -2)) {
258 const char *value
= lua_tostring(ps
, -1);
265 strcat(query
, value
);
272 strcat(query
, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
274 uui
= malloc(sizeof(*uui
));
276 uui
->update
= strdup(query
);
277 uui
->channelid
= channelid
;
278 uui
->account
= strdup(account
);
279 uui
->accountid
= accountid
;
281 a4stats_update_user_cb(NULL
, uui
);
283 LUA_RETURN(ps
, LUA_OK
);
286 typedef struct relation_update_info
{
288 unsigned long channelid
;
290 unsigned long firstid
;
292 unsigned long secondid
;
293 } relation_update_info
;
295 static void a4stats_update_relation_cb(const struct DBAPIResult
*result
, void *uarg
) {
296 relation_update_info
*rui
= uarg
;
300 if (rui
->stage
== 1) {
301 a4statsdb
->query(a4statsdb
, a4stats_update_relation_cb
, rui
, "UPDATE ? SET score = score + 1, seen = ? "
302 "WHERE channelid = ? AND first = ? AND firstid = ? AND second = ? AND secondid = ?",
303 "TtUsUsU", "relations", time(NULL
), rui
->channelid
, rui
->first
, rui
->firstid
, rui
->second
, rui
->secondid
);
305 } else if (rui
->stage
== 2 && result
&& result
->affected
== 0) {
306 a4statsdb
->query(a4statsdb
, a4stats_update_relation_cb
, rui
, "INSERT INTO ? (channelid, first, firstid, second, secondid, seen) VALUES (?, ?, ?, ?, ?, ?)",
307 "TUsUsUt", "relations", rui
->channelid
, rui
->first
, rui
->firstid
, rui
->second
, rui
->secondid
, time(NULL
));
311 if (!result
|| result
->affected
== 0)
312 Error("a4stats", ERR_WARNING
, "Unable to update relation.");
320 result
->clear(result
);
323 static int a4stats_lua_update_relation(lua_State
*ps
) {
324 const char *user1
, *user2
;
325 unsigned long channelid
, user1id
, user2id
;
326 relation_update_info
*rui
;
328 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3) || !lua_isstring(ps
, 4) || !lua_isnumber(ps
, 5))
329 LUA_RETURN(ps
, LUA_FAIL
);
331 channelid
= lua_tonumber(ps
, 1);
332 user1
= lua_tostring(ps
, 2);
333 user1id
= lua_tonumber(ps
, 3);
334 user2
= lua_tostring(ps
, 4);
335 user2id
= lua_tonumber(ps
, 5);
337 rui
= malloc(sizeof(*rui
));
339 rui
->channelid
= channelid
;
341 if (user1id
< user2id
|| (user1id
== user2id
&& strcmp(user1
, user2
) <= 0)) {
342 rui
->first
= strdup(user1
);
343 rui
->firstid
= user1id
;
344 rui
->second
= strdup(user2
);
345 rui
->secondid
= user2id
;
347 rui
->first
= strdup(user2
);
348 rui
->firstid
= user2id
;
349 rui
->second
= strdup(user1
);
350 rui
->secondid
= user1id
;
353 a4stats_update_relation_cb(NULL
, rui
);
355 LUA_RETURN(ps
, LUA_OK
);
358 static int a4stats_lua_add_line(lua_State
*ps
) {
363 if (!lua_isstring(ps
, 1) || !lua_isnumber(ps
, 2))
364 LUA_RETURN(ps
, LUA_FAIL
);
366 channel
= lua_tostring(ps
, 1);
367 hour
= lua_tonumber(ps
, 2);
369 snprintf(query
, sizeof(query
), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour
, hour
);
371 a4statsdb
->squery(a4statsdb
, query
, "Ts", "channels", channel
);
373 LUA_RETURN(ps
, LUA_OK
);
378 unsigned long pending
;
379 unsigned long topicskicks
;
380 unsigned long disabled
;
381 unsigned long deleted
;
384 static void a4stats_cleanupdb_got_result(void) {
385 if (!--cleanupdata
.pending
) {
386 controlwall(NO_OPER
, NL_CLEANUP
, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.",
387 cleanupdata
.topicskicks
, cleanupdata
.disabled
, cleanupdata
.deleted
);
388 cleanupdata
.start
= 0;
392 static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult
*result
, void *arg
) {
393 unsigned long *counter
= arg
;
396 *counter
+= result
->affected
;
397 result
->clear(result
);
400 a4stats_cleanupdb_got_result();
403 static void a4stats_cleanupdb_cb_active(const struct DBAPIResult
*result
, void *null
) {
404 unsigned long channelid
;
410 if (result
->success
) {
411 while (result
->next(result
)) {
412 channelid
= strtoul(result
->get(result
, 0), NULL
, 10);
413 seen
= (time_t)strtoul(result
->get(result
, 2), NULL
, 10);
414 /* use channel enabling timestamp if there was never any event */
416 seen
= (time_t)strtoul(result
->get(result
, 1), NULL
, 10);
418 if (seen
< cleanupdata
.start
- CLEANUP_INACTIVE_DAYS
* 86400) {
419 /* disable inactive channels */
420 cleanupdata
.pending
++;
421 a4statsdb
->query(a4statsdb
, a4stats_cleanupdb_cb_countrows
, &cleanupdata
.disabled
,
422 "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1",
423 "TtU", "channels", cleanupdata
.start
, channelid
);
425 /* cleanup old kicks/topics */
426 cleanupdata
.pending
++;
427 a4statsdb
->query(a4statsdb
, a4stats_cleanupdb_cb_countrows
, &cleanupdata
.topicskicks
,
428 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
429 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
430 "TUTUU", "kicks", channelid
, "kicks", channelid
, (unsigned long)CLEANUP_KEEP
);
431 cleanupdata
.pending
++;
432 a4statsdb
->query(a4statsdb
, a4stats_cleanupdb_cb_countrows
, &cleanupdata
.topicskicks
,
433 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
434 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
435 "TUTUU", "topics", channelid
, "topics", channelid
, (unsigned long)CLEANUP_KEEP
);
439 result
->clear(result
);
442 static void a4stats_cleanupdb(void *null
) {
443 controlwall(NO_OPER
, NL_CLEANUP
, "Starting a4stats_db cleanup.");
445 if (cleanupdata
.start
!= 0) {
446 controlwall(NO_OPER
, NL_CLEANUP
, "a4stats cleanup already in progress.");
450 cleanupdata
.start
= time(NULL
);
451 cleanupdata
.pending
= 0;
452 cleanupdata
.topicskicks
= 0;
453 cleanupdata
.disabled
= 0;
454 cleanupdata
.deleted
= 0;
456 a4statsdb
->query(a4statsdb
, a4stats_cleanupdb_cb_active
, NULL
,
457 "SELECT id, timestamp, MAX(users.seen) FROM ? LEFT JOIN ? AS users ON id = users.channelid WHERE active = 1 GROUP BY id",
458 "TT", "channels", "users");
459 cleanupdata
.pending
++;
460 a4statsdb
->query(a4statsdb
, a4stats_cleanupdb_cb_countrows
, &cleanupdata
.deleted
,
461 "DELETE FROM ? WHERE active = 0 AND deleted < ?", "Tt", "channels", (time_t)(cleanupdata
.start
- CLEANUP_DELETE_DAYS
* 84600));
464 static void a4stats_fetch_channels_cb(const struct DBAPIResult
*result
, void *uarg
) {
465 db_callback_info
*dci
= uarg
;
466 unsigned long channelid
;
471 if (result
->success
) {
472 while (result
->next(result
)) {
473 channelid
= strtoul(result
->get(result
, 0), NULL
, 10);
474 channel
= result
->get(result
, 1);
475 active
= atoi(result
->get(result
, 2));
478 lua_vpcall(dci
->interp
, dci
->callback
, "lsiR", channelid
, channel
, active
, dci
->uarg_index
);
482 result
->clear(result
);
486 luaL_unref(dci
->interp
, LUA_REGISTRYINDEX
, dci
->uarg_index
);
488 a4stats_delete_dci(dci
);
491 static int a4stats_lua_fetch_channels(lua_State
*ps
) {
492 const char *callback
;
493 db_callback_info
*dci
;
495 if (!lua_isstring(ps
, 1))
496 LUA_RETURN(ps
, LUA_FAIL
);
498 callback
= lua_tostring(ps
, 1);
500 dci
= malloc(sizeof(*dci
));
503 strncpy(dci
->callback
, callback
, sizeof(dci
->callback
));
504 dci
->callback
[sizeof(dci
->callback
) - 1] = '\0';
506 lua_pushvalue(ps
, 2);
507 dci
->uarg_index
= luaL_ref(ps
, LUA_REGISTRYINDEX
);
509 a4statsdb
->query(a4statsdb
, a4stats_fetch_channels_cb
, dci
, "SELECT id, name, active FROM ?", "T", "channels");
511 LUA_RETURN(ps
, LUA_OK
);
514 static int a4stats_lua_enable_channel(lua_State
*ps
) {
515 if (!lua_isstring(ps
, 1))
516 LUA_RETURN(ps
, LUA_FAIL
);
518 a4statsdb
->squery(a4statsdb
, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps
, 1), time(NULL
));
519 a4statsdb
->squery(a4statsdb
, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps
, 1));
521 LUA_RETURN(ps
, LUA_OK
);
524 static int a4stats_lua_disable_channel(lua_State
*ps
) {
525 if (!lua_isstring(ps
, 1))
526 LUA_RETURN(ps
, LUA_FAIL
);
528 a4statsdb
->squery(a4statsdb
, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL
), lua_tostring(ps
, 1));
530 LUA_RETURN(ps
, LUA_OK
);
533 static int a4stats_lua_add_kick(lua_State
*ps
) {
534 unsigned long channelid
, kickerid
, victimid
;
535 const char *kicker
, *victim
, *reason
;
537 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))
538 LUA_RETURN(ps
, LUA_FAIL
);
540 channelid
= lua_tonumber(ps
, 1);
541 kicker
= lua_tostring(ps
, 2);
542 kickerid
= lua_tonumber(ps
, 3);
543 victim
= lua_tostring(ps
, 4);
544 victimid
= lua_tonumber(ps
, 5);
545 reason
= lua_tostring(ps
, 6);
547 a4statsdb
->squery(a4statsdb
, "INSERT INTO ? (channelid, kicker, kickerid, victim, victimid, timestamp, reason) VALUES (?, ?, ?, ?, ?, ?, ?)", "TUsUsUts",
548 "kicks", channelid
, kicker
, kickerid
, victim
, victimid
, time(NULL
), reason
);
550 LUA_RETURN(ps
, LUA_OK
);
553 static int a4stats_lua_add_topic(lua_State
*ps
) {
554 unsigned long channelid
, setbyid
;
555 const char *topic
, *setby
;
557 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isstring(ps
, 3) || !lua_isnumber(ps
, 4))
558 LUA_RETURN(ps
, LUA_FAIL
);
560 channelid
= lua_tonumber(ps
, 1);
561 topic
= lua_tostring(ps
, 2);
562 setby
= lua_tostring(ps
, 3);
563 setbyid
= lua_tonumber(ps
, 4);
565 a4statsdb
->squery(a4statsdb
, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
566 "topics", channelid
, topic
, time(NULL
), setby
, setbyid
);
568 LUA_RETURN(ps
, LUA_OK
);
571 static void a4stats_hook_loadscript(int hooknum
, void *arg
) {
573 lua_State
*l
= args
[1];
575 lua_register(l
, "a4_enable_channel", a4stats_lua_enable_channel
);
576 lua_register(l
, "a4_disable_channel", a4stats_lua_disable_channel
);
577 lua_register(l
, "a4_fetch_channels", a4stats_lua_fetch_channels
);
578 lua_register(l
, "a4_add_kick", a4stats_lua_add_kick
);
579 lua_register(l
, "a4_add_topic", a4stats_lua_add_topic
);
580 lua_register(l
, "a4_add_line", a4stats_lua_add_line
);
581 lua_register(l
, "a4_fetch_user", a4stats_lua_fetch_user
);
582 lua_register(l
, "a4_update_user", a4stats_lua_update_user
);
583 lua_register(l
, "a4_update_relation", a4stats_lua_update_relation
);
584 lua_register(l
, "a4_escape_string", a4stats_lua_escape_string
);
587 #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
589 static void a4stats_hook_unloadscript(int hooknum
, void *arg
) {
590 db_callback_info
**pnext
, *dci
;
593 for (pnext
= &dci_head
; *pnext
; pnext
= &((*pnext
)->next
)) {
595 if (dci
->interp
== l
) {
608 registerhook(HOOK_LUA_LOADSCRIPT
, a4stats_hook_loadscript
);
609 registerhook(HOOK_LUA_UNLOADSCRIPT
, a4stats_hook_unloadscript
);
610 schedulerecurring(time(NULL
), 0, CLEANUP_INTERVAL
, a4stats_cleanupdb
, NULL
);
613 for (l
= lua_head
; l
;l
= l
->next
) {
615 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT
, args
);
622 deleteschedule(NULL
, a4stats_cleanupdb
, NULL
);
625 for (l
= lua_head
; l
;l
= l
->next
) {
626 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT
, l
->l
);
628 lua_unregister(l
->l
, "a4_enable_channel");
629 lua_unregister(l
->l
, "a4_disable_channel");
630 lua_unregister(l
->l
, "a4_fetch_channels");
631 lua_unregister(l
->l
, "a4_add_kick");
632 lua_unregister(l
->l
, "a4_add_topic");
633 lua_unregister(l
->l
, "a4_add_line");
634 lua_unregister(l
->l
, "a4_fetch_user");
635 lua_unregister(l
->l
, "a4_update_user");
636 lua_unregister(l
->l
, "a4_update_relation");
637 lua_unregister(l
->l
, "a4_escape_string");
640 deregisterhook(HOOK_LUA_LOADSCRIPT
, a4stats_hook_loadscript
);
641 deregisterhook(HOOK_LUA_UNLOADSCRIPT
, a4stats_hook_unloadscript
);