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 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");
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
));
234 static int a4stats_lua_update_user(lua_State
*ps
) {
236 unsigned long channelid
, accountid
;
239 user_update_info
*uui
;
241 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3))
242 LUA_RETURN(ps
, LUA_FAIL
);
244 channelid
= lua_tonumber(ps
, 1);
245 account
= lua_tostring(ps
, 2);
246 accountid
= lua_tonumber(ps
, 3);
248 strcpy(query
, "UPDATE ? SET ");
250 lua_pushvalue(ps
, 4);
253 while (lua_next(ps
, -2)) {
254 const char *value
= lua_tostring(ps
, -1);
261 strcat(query
, value
);
268 strcat(query
, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
270 uui
= malloc(sizeof(*uui
));
272 uui
->update
= strdup(query
);
273 uui
->channelid
= channelid
;
274 uui
->account
= strdup(account
);
275 uui
->accountid
= accountid
;
277 a4stats_update_user_cb(NULL
, uui
);
279 LUA_RETURN(ps
, LUA_OK
);
282 typedef struct relation_update_info
{
284 unsigned long channelid
;
286 unsigned long firstid
;
288 unsigned long secondid
;
289 } relation_update_info
;
291 static void a4stats_update_relation_cb(const struct DBAPIResult
*result
, void *uarg
) {
292 relation_update_info
*rui
= uarg
;
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
);
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
));
307 if (!result
|| result
->affected
== 0)
308 Error("a4stats", ERR_WARNING
, "Unable to update relation.");
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
;
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
);
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);
329 rui
= malloc(sizeof(*rui
));
331 rui
->channelid
= channelid
;
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
;
339 rui
->first
= strdup(user2
);
340 rui
->firstid
= user2id
;
341 rui
->second
= strdup(user1
);
342 rui
->secondid
= user1id
;
345 a4stats_update_relation_cb(NULL
, rui
);
347 LUA_RETURN(ps
, LUA_OK
);
350 static int a4stats_lua_add_line(lua_State
*ps
) {
355 if (!lua_isstring(ps
, 1) || !lua_isnumber(ps
, 2))
356 LUA_RETURN(ps
, LUA_FAIL
);
358 channel
= lua_tostring(ps
, 1);
359 hour
= lua_tonumber(ps
, 2);
361 snprintf(query
, sizeof(query
), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour
, hour
);
363 a4statsdb
->squery(a4statsdb
, query
, "Ts", "channels", channel
);
365 LUA_RETURN(ps
, LUA_OK
);
370 unsigned long pending
;
371 unsigned long topicskicks
;
372 unsigned long disabled
;
373 unsigned long deleted
;
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;
384 static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult
*result
, void *arg
) {
385 unsigned long *counter
= arg
;
388 *counter
+= result
->affected
;
390 a4stats_cleanupdb_got_result();
393 static void a4stats_cleanupdb_cb_active(const struct DBAPIResult
*result
, void *null
) {
394 unsigned long channelid
;
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 */
403 seen
= (time_t)strtoul(result
->get(result
, 1), NULL
, 10);
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
);
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
);
428 static void a4stats_cleanupdb(void *null
) {
429 controlwall(NO_OPER
, NL_CLEANUP
, "Starting a4stats_db cleanup.");
431 if (cleanupdata
.start
!= 0) {
432 controlwall(NO_OPER
, NL_CLEANUP
, "a4stats cleanup already in progress.");
436 cleanupdata
.start
= time(NULL
);
437 cleanupdata
.pending
= 0;
438 cleanupdata
.topicskicks
= 0;
439 cleanupdata
.disabled
= 0;
440 cleanupdata
.deleted
= 0;
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));
450 static void a4stats_fetch_channels_cb(const struct DBAPIResult
*result
, void *uarg
) {
451 db_callback_info
*dci
= uarg
;
452 unsigned long channelid
;
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));
464 lua_vpcall(dci
->interp
, dci
->callback
, "lsiR", channelid
, channel
, active
, dci
->uarg_index
);
468 result
->clear(result
);
472 luaL_unref(dci
->interp
, LUA_REGISTRYINDEX
, dci
->uarg_index
);
474 a4stats_delete_dci(dci
);
477 static int a4stats_lua_fetch_channels(lua_State
*ps
) {
478 const char *callback
;
479 db_callback_info
*dci
;
481 if (!lua_isstring(ps
, 1))
482 LUA_RETURN(ps
, LUA_FAIL
);
484 callback
= lua_tostring(ps
, 1);
486 dci
= malloc(sizeof(*dci
));
489 strncpy(dci
->callback
, callback
, sizeof(dci
->callback
));
490 dci
->callback
[sizeof(dci
->callback
) - 1] = '\0';
492 lua_pushvalue(ps
, 2);
493 dci
->uarg_index
= luaL_ref(ps
, LUA_REGISTRYINDEX
);
495 a4statsdb
->query(a4statsdb
, a4stats_fetch_channels_cb
, dci
, "SELECT id, name, active FROM ?", "T", "channels");
497 LUA_RETURN(ps
, LUA_OK
);
500 static int a4stats_lua_enable_channel(lua_State
*ps
) {
501 if (!lua_isstring(ps
, 1))
502 LUA_RETURN(ps
, LUA_FAIL
);
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));
507 LUA_RETURN(ps
, LUA_OK
);
510 static int a4stats_lua_disable_channel(lua_State
*ps
) {
511 if (!lua_isstring(ps
, 1))
512 LUA_RETURN(ps
, LUA_FAIL
);
514 a4statsdb
->squery(a4statsdb
, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL
), lua_tostring(ps
, 1));
516 LUA_RETURN(ps
, LUA_OK
);
519 static int a4stats_lua_add_kick(lua_State
*ps
) {
520 unsigned long channelid
, kickerid
, victimid
;
521 const char *kicker
, *victim
, *reason
;
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
);
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);
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
);
536 LUA_RETURN(ps
, LUA_OK
);
539 static int a4stats_lua_add_topic(lua_State
*ps
) {
540 unsigned long channelid
, setbyid
;
541 const char *topic
, *setby
;
543 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isstring(ps
, 3) || !lua_isnumber(ps
, 4))
544 LUA_RETURN(ps
, LUA_FAIL
);
546 channelid
= lua_tonumber(ps
, 1);
547 topic
= lua_tostring(ps
, 2);
548 setby
= lua_tostring(ps
, 3);
549 setbyid
= lua_tonumber(ps
, 4);
551 a4statsdb
->squery(a4statsdb
, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
552 "topics", channelid
, topic
, time(NULL
), setby
, setbyid
);
554 LUA_RETURN(ps
, LUA_OK
);
557 static void a4stats_hook_loadscript(int hooknum
, void *arg
) {
559 lua_State
*l
= args
[1];
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
);
573 #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
575 static void a4stats_hook_unloadscript(int hooknum
, void *arg
) {
576 db_callback_info
**pnext
, *dci
;
579 for (pnext
= &dci_head
; *pnext
; pnext
= &((*pnext
)->next
)) {
581 if (dci
->interp
== l
) {
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
);
599 for (l
= lua_head
; l
;l
= l
->next
) {
601 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT
, args
);
608 deleteschedule(NULL
, a4stats_cleanupdb
, NULL
);
611 for (l
= lua_head
; l
;l
= l
->next
) {
612 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT
, l
->l
);
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");
626 deregisterhook(HOOK_LUA_LOADSCRIPT
, a4stats_hook_loadscript
);
627 deregisterhook(HOOK_LUA_UNLOADSCRIPT
, a4stats_hook_unloadscript
);