]> jfr.im git - irc/quakenet/newserv.git/blame - a4stats/a4stats_db.c
a4stats: Fix cleanup interval to be one day.
[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
5905e45f 12#define CLEANUP_KEEP 10 /* keep this many topics and kicks per channel around */
ef90464f 13#define CLEANUP_INTERVAL 86400 /* db cleanup interval (in seconds) */
82645134 14#define CLEANUP_INACTIVE_DAYS 30 /* disable channels where nothing happened for this many days */
5af6ffcf 15#define CLEANUP_DELETE_DAYS 5 /* delete data for channels that have been disabled for this many days */
5905e45f 16
d5c004ba
GB
17MODULE_VERSION("");
18
19DBAPIConn *a4statsdb;
20
21static int a4stats_connectdb(void) {
22 if(!a4statsdb) {
cf5ac373 23 a4statsdb = dbapi2open("pqsql", "a4stats");
d5c004ba
GB
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,
82645134 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, "
92898969
GB
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");
d5c004ba 36
f79faea1 37 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
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");
f79faea1 40
cf5ac373
GB
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");
d5c004ba
GB
43
44 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
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");
d5c004ba 47
cf5ac373 48 a4statsdb->squery(a4statsdb, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics");
d5c004ba
GB
49
50 a4statsdb->createtable(a4statsdb, NULL, NULL,
cf5ac373 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, "
f79faea1
GB
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, "
e0311e96 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, "
d5c004ba 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, "
5af6ffcf 58 "firstseen INT DEFAULT 0, curnick VARCHAR(16), FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "users", "channels");
d5c004ba 59
cf5ac373
GB
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");
d5c004ba 65
28d6a377 66 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
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");
28d6a377 69
8d6e0508
GB
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
d5c004ba
GB
74 return 1;
75}
76
77static void a4stats_closedb(void) {
78 if(!a4statsdb)
79 return;
80
81 a4statsdb->close(a4statsdb);
82 a4statsdb = NULL;
83}
84
85static 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
f79faea1
GB
131typedef struct db_callback_info {
132 struct db_callback_info *next;
d5c004ba
GB
133
134 lua_State *interp;
135 char callback[64];
136 int uarg_index;
f79faea1
GB
137} db_callback_info;
138
139static db_callback_info *dci_head;
140
141static void a4stats_delete_dci(db_callback_info *dci) {
5905e45f 142 db_callback_info **pnext;
d5c004ba 143
f79faea1
GB
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}
d5c004ba
GB
153
154static void a4stats_fetch_user_cb(const struct DBAPIResult *result, void *uarg) {
f79faea1 155 db_callback_info *dci = uarg;
d5c004ba
GB
156 time_t seen = 0, quotereset = 0;
157
158 if (result) {
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;
163 }
164 }
165
166 result->clear(result);
167 }
168
f79faea1 169 if (dci->interp) {
cf5ac373 170 lua_vpcall(dci->interp, dci->callback, "llR", (long)seen, (long)quotereset, dci->uarg_index);
f79faea1 171 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
d5c004ba
GB
172 }
173
f79faea1 174 a4stats_delete_dci(dci);
d5c004ba
GB
175}
176
177static int a4stats_lua_fetch_user(lua_State *ps) {
f79faea1
GB
178 const char *account, *callback;
179 unsigned long channelid, accountid;
180 db_callback_info *dci;
d5c004ba 181
f79faea1 182 if (!lua_islong(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4))
d5c004ba
GB
183 LUA_RETURN(ps, LUA_FAIL);
184
f79faea1 185 channelid = lua_tonumber(ps, 1);
d5c004ba
GB
186 account = lua_tostring(ps, 2);
187 accountid = lua_tonumber(ps, 3);
188 callback = lua_tostring(ps, 4);
189
f79faea1
GB
190 dci = malloc(sizeof(*dci));
191 dci->interp = ps;
d5c004ba 192
f79faea1
GB
193 strncpy(dci->callback, callback, sizeof(dci->callback));
194 dci->callback[sizeof(dci->callback) - 1] = '\0';
d5c004ba
GB
195
196 lua_pushvalue(ps, 5);
f79faea1 197 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
d5c004ba 198
f79faea1 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);
d5c004ba
GB
200
201 LUA_RETURN(ps, LUA_OK);
202}
203
204typedef struct user_update_info {
205 int stage;
206 char *update;
f79faea1 207 unsigned long channelid;
d5c004ba
GB
208 char *account;
209 unsigned long accountid;
210} user_update_info;
211
212static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) {
213 user_update_info *uui = uarg;
214
215 uui->stage++;
216
e6a4eec1 217 if (uui->stage == 1 || (result != NULL && uui->stage == 3))
f79faea1 218 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account);
d5c004ba 219 else {
e6a4eec1 220 if (result == NULL || result->affected > 0 || uui->stage == 4) {
424b0e83 221 if (result == NULL || (result->affected == 0 && uui->stage == 4))
d5c004ba
GB
222 Error("a4stats", ERR_WARNING, "Unable to update user.");
223
224 free(uui->update);
d5c004ba
GB
225 free(uui->account);
226 free(uui);
227 return;
228 }
229
f79faea1 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));
d5c004ba
GB
231 }
232}
233
234static int a4stats_lua_update_user(lua_State *ps) {
f79faea1
GB
235 const char *account;
236 unsigned long channelid, accountid;
d5c004ba
GB
237 char query[4096];
238 int first = 1;
239 user_update_info *uui;
240
f79faea1 241 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3))
d5c004ba
GB
242 LUA_RETURN(ps, LUA_FAIL);
243
f79faea1 244 channelid = lua_tonumber(ps, 1);
d5c004ba
GB
245 account = lua_tostring(ps, 2);
246 accountid = lua_tonumber(ps, 3);
247
248 strcpy(query, "UPDATE ? SET ");
249
250 lua_pushvalue(ps, 4);
251 lua_pushnil(ps);
252
253 while (lua_next(ps, -2)) {
254 const char *value = lua_tostring(ps, -1);
255
256 if (first)
257 first = 0;
258 else
259 strcat(query, ", ");
260
261 strcat(query, value);
262
263 lua_pop(ps, 1);
264 }
265
266 lua_pop(ps, 1);
267
f79faea1 268 strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
d5c004ba
GB
269
270 uui = malloc(sizeof(*uui));
271 uui->stage = 0;
272 uui->update = strdup(query);
f79faea1 273 uui->channelid = channelid;
d5c004ba
GB
274 uui->account = strdup(account);
275 uui->accountid = accountid;
276
277 a4stats_update_user_cb(NULL, uui);
278
279 LUA_RETURN(ps, LUA_OK);
280}
281
28d6a377
GB
282typedef struct relation_update_info {
283 int stage;
284 unsigned long channelid;
285 char *first;
286 unsigned long firstid;
287 char *second;
288 unsigned long secondid;
289} relation_update_info;
290
291static void a4stats_update_relation_cb(const struct DBAPIResult *result, void *uarg) {
292 relation_update_info *rui = uarg;
293
294 rui->stage++;
295
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);
300 return;
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));
304 return;
305 }
306
307 if (!result || result->affected == 0)
308 Error("a4stats", ERR_WARNING, "Unable to update relation.");
309
310 free(rui->first);
311 free(rui->second);
312 free(rui);
313}
314
315static int a4stats_lua_update_relation(lua_State *ps) {
316 const char *user1, *user2;
317 unsigned long channelid, user1id, user2id;
318 relation_update_info *rui;
319
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);
322
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);
328
329 rui = malloc(sizeof(*rui));
330 rui->stage = 0;
331 rui->channelid = channelid;
332
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;
338 } else {
339 rui->first = strdup(user2);
340 rui->firstid = user2id;
341 rui->second = strdup(user1);
342 rui->secondid = user1id;
343 }
344
345 a4stats_update_relation_cb(NULL, rui);
346
347 LUA_RETURN(ps, LUA_OK);
348}
349
92898969
GB
350static int a4stats_lua_add_line(lua_State *ps) {
351 char query[256];
352 const char *channel;
353 int hour;
354
355 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
356 LUA_RETURN(ps, LUA_FAIL);
357
358 channel = lua_tostring(ps, 1);
359 hour = lua_tonumber(ps, 2);
360
361 snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour, hour);
362
363 a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel);
364
365 LUA_RETURN(ps, LUA_OK);
366}
367
5af6ffcf
TS
368static struct {
369 time_t start;
82645134
TS
370 unsigned long pending;
371 unsigned long topicskicks;
372 unsigned long disabled;
5af6ffcf 373 unsigned long deleted;
82645134
TS
374} cleanupdata;
375
5af6ffcf
TS
376static void a4stats_cleanupdb_got_result(void) {
377 if (!--cleanupdata.pending) {
23c149d9 378 controlwall(NO_OPER, NL_CLEANUP, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.",
5af6ffcf
TS
379 cleanupdata.topicskicks, cleanupdata.disabled, cleanupdata.deleted);
380 cleanupdata.start = 0;
381 }
82645134
TS
382}
383
5af6ffcf
TS
384static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult *result, void *arg) {
385 unsigned long *counter = arg;
82645134
TS
386
387 if (result)
5af6ffcf 388 *counter += result->affected;
82645134 389
5af6ffcf 390 a4stats_cleanupdb_got_result();
5905e45f
TS
391}
392
5af6ffcf 393static void a4stats_cleanupdb_cb_active(const struct DBAPIResult *result, void *null) {
82645134
TS
394 unsigned long channelid;
395 time_t seen;
5905e45f
TS
396
397 if (result && result->success) {
398 while (result->next(result)) {
399 channelid = strtoul(result->get(result, 0), NULL, 10);
82645134
TS
400 seen = (time_t)strtoul(result->get(result, 2), NULL, 10);
401 /* use channel enabling timestamp if there was never any event */
402 if (!seen)
403 seen = (time_t)strtoul(result->get(result, 1), NULL, 10);
404
5af6ffcf
TS
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,
82645134 409 "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1",
5af6ffcf 410 "TtU", "channels", cleanupdata.start, channelid);
82645134
TS
411 } else {
412 /* cleanup old kicks/topics */
5af6ffcf
TS
413 cleanupdata.pending++;
414 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
82645134
TS
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);
5af6ffcf
TS
418 cleanupdata.pending++;
419 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
82645134
TS
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);
423 }
5905e45f
TS
424 }
425 }
426}
427
428static void a4stats_cleanupdb(void *null) {
5af6ffcf 429 controlwall(NO_OPER, NL_CLEANUP, "Starting a4stats_db cleanup.");
82645134 430
5af6ffcf
TS
431 if (cleanupdata.start != 0) {
432 controlwall(NO_OPER, NL_CLEANUP, "a4stats cleanup already in progress.");
433 return;
434 }
5905e45f 435
5af6ffcf
TS
436 cleanupdata.start = time(NULL);
437 cleanupdata.pending = 0;
438 cleanupdata.topicskicks = 0;
439 cleanupdata.disabled = 0;
440 cleanupdata.deleted = 0;
441
442 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_active, NULL,
82645134
TS
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");
5af6ffcf
TS
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));
5905e45f
TS
448}
449
f79faea1
GB
450static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) {
451 db_callback_info *dci = uarg;
452 unsigned long channelid;
453 int active;
454 char *channel;
455
456 if (result) {
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));
462
463 if (dci->interp)
cf5ac373 464 lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index);
f79faea1
GB
465 }
466 }
467
468 result->clear(result);
469 }
470
471 if (dci->interp)
472 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
473
474 a4stats_delete_dci(dci);
475}
476
477static int a4stats_lua_fetch_channels(lua_State *ps) {
478 const char *callback;
479 db_callback_info *dci;
480
481 if (!lua_isstring(ps, 1))
482 LUA_RETURN(ps, LUA_FAIL);
483
484 callback = lua_tostring(ps, 1);
485
486 dci = malloc(sizeof(*dci));
487 dci->interp = ps;
488
489 strncpy(dci->callback, callback, sizeof(dci->callback));
490 dci->callback[sizeof(dci->callback) - 1] = '\0';
491
492 lua_pushvalue(ps, 2);
493 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
494
495 a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels");
496
497 LUA_RETURN(ps, LUA_OK);
498}
499
5bf3ab9d 500static int a4stats_lua_enable_channel(lua_State *ps) {
f79faea1
GB
501 if (!lua_isstring(ps, 1))
502 LUA_RETURN(ps, LUA_FAIL);
503
899f2ee4 504 a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL));
c2a4bd5a 505 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps, 1));
5bf3ab9d
GB
506
507 LUA_RETURN(ps, LUA_OK);
508}
509
510static int a4stats_lua_disable_channel(lua_State *ps) {
511 if (!lua_isstring(ps, 1))
512 LUA_RETURN(ps, LUA_FAIL);
513
c2a4bd5a 514 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL), lua_tostring(ps, 1));
f79faea1
GB
515
516 LUA_RETURN(ps, LUA_OK);
517}
518
d5c004ba 519static int a4stats_lua_add_kick(lua_State *ps) {
f79faea1
GB
520 unsigned long channelid, kickerid, victimid;
521 const char *kicker, *victim, *reason;
522
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))
d5c004ba
GB
524 LUA_RETURN(ps, LUA_FAIL);
525
f79faea1
GB
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);
532
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);
d5c004ba
GB
535
536 LUA_RETURN(ps, LUA_OK);
537}
538
539static int a4stats_lua_add_topic(lua_State *ps) {
f79faea1
GB
540 unsigned long channelid, setbyid;
541 const char *topic, *setby;
542
543 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4))
d5c004ba
GB
544 LUA_RETURN(ps, LUA_FAIL);
545
f79faea1
GB
546 channelid = lua_tonumber(ps, 1);
547 topic = lua_tostring(ps, 2);
548 setby = lua_tostring(ps, 3);
549 setbyid = lua_tonumber(ps, 4);
550
551 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
552 "topics", channelid, topic, time(NULL), setby, setbyid);
d5c004ba
GB
553
554 LUA_RETURN(ps, LUA_OK);
555}
556
557static void a4stats_hook_loadscript(int hooknum, void *arg) {
558 void **args = arg;
559 lua_State *l = args[1];
560
5bf3ab9d
GB
561 lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel);
562 lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel);
f79faea1 563 lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels);
d5c004ba
GB
564 lua_register(l, "a4_add_kick", a4stats_lua_add_kick);
565 lua_register(l, "a4_add_topic", a4stats_lua_add_topic);
92898969 566 lua_register(l, "a4_add_line", a4stats_lua_add_line);
d5c004ba
GB
567 lua_register(l, "a4_fetch_user", a4stats_lua_fetch_user);
568 lua_register(l, "a4_update_user", a4stats_lua_update_user);
28d6a377 569 lua_register(l, "a4_update_relation", a4stats_lua_update_relation);
d5c004ba
GB
570 lua_register(l, "a4_escape_string", a4stats_lua_escape_string);
571}
572
573#define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
574
575static void a4stats_hook_unloadscript(int hooknum, void *arg) {
f79faea1 576 db_callback_info **pnext, *dci;
899f2ee4 577 lua_State *l = arg;
d5c004ba 578
f79faea1
GB
579 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
580 dci = *pnext;
899f2ee4 581 if (dci->interp == l) {
f79faea1
GB
582 *pnext = dci->next;
583 free(dci);
d5c004ba
GB
584 }
585 }
d5c004ba
GB
586}
587
588void _init(void) {
589 lua_list *l;
590 void *args[2];
591
592 a4stats_connectdb();
593
594 registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
595 registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
5905e45f 596 schedulerecurring(time(NULL), 0, CLEANUP_INTERVAL, a4stats_cleanupdb, NULL);
d5c004ba
GB
597
598 args[0] = NULL;
599 for (l = lua_head; l;l = l->next) {
600 args[1] = l->l;
601 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args);
602 }
603}
604
605void _fini(void) {
f79faea1
GB
606 lua_list *l;
607
5905e45f 608 deleteschedule(NULL, a4stats_cleanupdb, NULL);
d5c004ba
GB
609 a4stats_closedb();
610
f79faea1 611 for (l = lua_head; l;l = l->next) {
064db467 612 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT, l->l);
899f2ee4
GB
613
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");
92898969 619 lua_unregister(l->l, "a4_add_line");
899f2ee4
GB
620 lua_unregister(l->l, "a4_fetch_user");
621 lua_unregister(l->l, "a4_update_user");
28d6a377 622 lua_unregister(l->l, "a4_update_relation");
899f2ee4 623 lua_unregister(l->l, "a4_escape_string");
f79faea1
GB
624 }
625
d5c004ba
GB
626 deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
627 deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
628}
629