]> jfr.im git - irc/quakenet/newserv.git/blame - a4stats/a4stats_db.c
Merge chanserv-live into default.
[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,
53c0b4a2 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, "
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 153
d5c004ba
GB
154typedef struct user_update_info {
155 int stage;
156 char *update;
f79faea1 157 unsigned long channelid;
d5c004ba
GB
158 char *account;
159 unsigned long accountid;
160} user_update_info;
161
162static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) {
163 user_update_info *uui = uarg;
164
165 uui->stage++;
166
e6a4eec1 167 if (uui->stage == 1 || (result != NULL && uui->stage == 3))
f79faea1 168 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account);
d5c004ba 169 else {
e6a4eec1 170 if (result == NULL || result->affected > 0 || uui->stage == 4) {
424b0e83 171 if (result == NULL || (result->affected == 0 && uui->stage == 4))
d5c004ba
GB
172 Error("a4stats", ERR_WARNING, "Unable to update user.");
173
174 free(uui->update);
d5c004ba
GB
175 free(uui->account);
176 free(uui);
316ff6c6 177 goto a4_uuc_return;
d5c004ba
GB
178 }
179
f79faea1 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));
d5c004ba 181 }
316ff6c6
TS
182
183a4_uuc_return:
184 if (result)
185 result->clear(result);
d5c004ba
GB
186}
187
188static int a4stats_lua_update_user(lua_State *ps) {
f79faea1
GB
189 const char *account;
190 unsigned long channelid, accountid;
d5c004ba
GB
191 char query[4096];
192 int first = 1;
193 user_update_info *uui;
194
f79faea1 195 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3))
d5c004ba
GB
196 LUA_RETURN(ps, LUA_FAIL);
197
f79faea1 198 channelid = lua_tonumber(ps, 1);
d5c004ba
GB
199 account = lua_tostring(ps, 2);
200 accountid = lua_tonumber(ps, 3);
201
202 strcpy(query, "UPDATE ? SET ");
203
204 lua_pushvalue(ps, 4);
205 lua_pushnil(ps);
206
207 while (lua_next(ps, -2)) {
208 const char *value = lua_tostring(ps, -1);
209
210 if (first)
211 first = 0;
212 else
213 strcat(query, ", ");
214
215 strcat(query, value);
216
217 lua_pop(ps, 1);
218 }
219
220 lua_pop(ps, 1);
221
f79faea1 222 strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
d5c004ba
GB
223
224 uui = malloc(sizeof(*uui));
225 uui->stage = 0;
226 uui->update = strdup(query);
f79faea1 227 uui->channelid = channelid;
d5c004ba
GB
228 uui->account = strdup(account);
229 uui->accountid = accountid;
230
231 a4stats_update_user_cb(NULL, uui);
232
233 LUA_RETURN(ps, LUA_OK);
234}
235
28d6a377
GB
236typedef struct relation_update_info {
237 int stage;
238 unsigned long channelid;
239 char *first;
240 unsigned long firstid;
241 char *second;
242 unsigned long secondid;
243} relation_update_info;
244
245static void a4stats_update_relation_cb(const struct DBAPIResult *result, void *uarg) {
246 relation_update_info *rui = uarg;
247
248 rui->stage++;
249
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);
316ff6c6 254 goto a4_urc_return;
28d6a377
GB
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));
316ff6c6 258 goto a4_urc_return;
28d6a377
GB
259 }
260
261 if (!result || result->affected == 0)
262 Error("a4stats", ERR_WARNING, "Unable to update relation.");
263
264 free(rui->first);
265 free(rui->second);
266 free(rui);
316ff6c6
TS
267
268a4_urc_return:
269 if (result)
270 result->clear(result);
28d6a377
GB
271}
272
273static int a4stats_lua_update_relation(lua_State *ps) {
274 const char *user1, *user2;
275 unsigned long channelid, user1id, user2id;
276 relation_update_info *rui;
277
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);
280
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);
286
287 rui = malloc(sizeof(*rui));
288 rui->stage = 0;
289 rui->channelid = channelid;
290
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;
296 } else {
297 rui->first = strdup(user2);
298 rui->firstid = user2id;
299 rui->second = strdup(user1);
300 rui->secondid = user1id;
301 }
302
303 a4stats_update_relation_cb(NULL, rui);
304
305 LUA_RETURN(ps, LUA_OK);
306}
307
92898969
GB
308static int a4stats_lua_add_line(lua_State *ps) {
309 char query[256];
310 const char *channel;
311 int hour;
312
313 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
314 LUA_RETURN(ps, LUA_FAIL);
315
316 channel = lua_tostring(ps, 1);
317 hour = lua_tonumber(ps, 2);
318
319 snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour, hour);
320
321 a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel);
322
323 LUA_RETURN(ps, LUA_OK);
324}
325
5af6ffcf
TS
326static struct {
327 time_t start;
82645134
TS
328 unsigned long pending;
329 unsigned long topicskicks;
330 unsigned long disabled;
5af6ffcf 331 unsigned long deleted;
82645134
TS
332} cleanupdata;
333
5af6ffcf
TS
334static void a4stats_cleanupdb_got_result(void) {
335 if (!--cleanupdata.pending) {
23c149d9 336 controlwall(NO_OPER, NL_CLEANUP, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.",
5af6ffcf
TS
337 cleanupdata.topicskicks, cleanupdata.disabled, cleanupdata.deleted);
338 cleanupdata.start = 0;
339 }
82645134
TS
340}
341
5af6ffcf
TS
342static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult *result, void *arg) {
343 unsigned long *counter = arg;
82645134 344
8a12ea21 345 if (result) {
5af6ffcf 346 *counter += result->affected;
8a12ea21
TS
347 result->clear(result);
348 }
82645134 349
5af6ffcf 350 a4stats_cleanupdb_got_result();
5905e45f
TS
351}
352
5af6ffcf 353static void a4stats_cleanupdb_cb_active(const struct DBAPIResult *result, void *null) {
82645134
TS
354 unsigned long channelid;
355 time_t seen;
8a12ea21
TS
356
357 if (!result)
358 return;
359
360 if (result->success) {
5905e45f
TS
361 while (result->next(result)) {
362 channelid = strtoul(result->get(result, 0), NULL, 10);
82645134
TS
363 seen = (time_t)strtoul(result->get(result, 2), NULL, 10);
364 /* use channel enabling timestamp if there was never any event */
365 if (!seen)
366 seen = (time_t)strtoul(result->get(result, 1), NULL, 10);
367
5af6ffcf
TS
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,
82645134 372 "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1",
5af6ffcf 373 "TtU", "channels", cleanupdata.start, channelid);
82645134
TS
374 } else {
375 /* cleanup old kicks/topics */
5af6ffcf
TS
376 cleanupdata.pending++;
377 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
82645134
TS
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);
5af6ffcf
TS
381 cleanupdata.pending++;
382 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
82645134
TS
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);
386 }
5905e45f
TS
387 }
388 }
8a12ea21 389 result->clear(result);
5905e45f
TS
390}
391
392static void a4stats_cleanupdb(void *null) {
5af6ffcf 393 controlwall(NO_OPER, NL_CLEANUP, "Starting a4stats_db cleanup.");
82645134 394
5af6ffcf
TS
395 if (cleanupdata.start != 0) {
396 controlwall(NO_OPER, NL_CLEANUP, "a4stats cleanup already in progress.");
397 return;
398 }
5905e45f 399
5af6ffcf
TS
400 cleanupdata.start = time(NULL);
401 cleanupdata.pending = 0;
402 cleanupdata.topicskicks = 0;
403 cleanupdata.disabled = 0;
404 cleanupdata.deleted = 0;
405
406 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_active, NULL,
82645134
TS
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");
5af6ffcf
TS
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));
5905e45f
TS
412}
413
f79faea1
GB
414static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) {
415 db_callback_info *dci = uarg;
416 unsigned long channelid;
417 int active;
418 char *channel;
419
420 if (result) {
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));
426
427 if (dci->interp)
cf5ac373 428 lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index);
f79faea1
GB
429 }
430 }
431
432 result->clear(result);
433 }
434
435 if (dci->interp)
436 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
437
438 a4stats_delete_dci(dci);
439}
440
441static int a4stats_lua_fetch_channels(lua_State *ps) {
442 const char *callback;
443 db_callback_info *dci;
444
445 if (!lua_isstring(ps, 1))
446 LUA_RETURN(ps, LUA_FAIL);
447
448 callback = lua_tostring(ps, 1);
449
450 dci = malloc(sizeof(*dci));
451 dci->interp = ps;
452
453 strncpy(dci->callback, callback, sizeof(dci->callback));
454 dci->callback[sizeof(dci->callback) - 1] = '\0';
455
456 lua_pushvalue(ps, 2);
457 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
458
459 a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels");
460
461 LUA_RETURN(ps, LUA_OK);
462}
463
5bf3ab9d 464static int a4stats_lua_enable_channel(lua_State *ps) {
f79faea1
GB
465 if (!lua_isstring(ps, 1))
466 LUA_RETURN(ps, LUA_FAIL);
467
899f2ee4 468 a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL));
c2a4bd5a 469 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps, 1));
5bf3ab9d
GB
470
471 LUA_RETURN(ps, LUA_OK);
472}
473
474static int a4stats_lua_disable_channel(lua_State *ps) {
475 if (!lua_isstring(ps, 1))
476 LUA_RETURN(ps, LUA_FAIL);
477
c2a4bd5a 478 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL), lua_tostring(ps, 1));
f79faea1
GB
479
480 LUA_RETURN(ps, LUA_OK);
481}
482
1b086b45
TS
483static int a4stats_lua_set_privacy(lua_State *ps) {
484 const char *channel;
485 unsigned long privacy;
486
487 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
488 LUA_RETURN(ps, LUA_FAIL);
489
490 channel = lua_tostring(ps, 1);
491 privacy = lua_tonumber(ps, 2);
492
493 a4statsdb->squery(a4statsdb, "UPDATE ? SET privacy = ? WHERE name = ?", "TUs", "channels", privacy, channel);
494 LUA_RETURN(ps, LUA_OK);
495}
496
d5c004ba 497static int a4stats_lua_add_kick(lua_State *ps) {
f79faea1
GB
498 unsigned long channelid, kickerid, victimid;
499 const char *kicker, *victim, *reason;
500
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))
d5c004ba
GB
502 LUA_RETURN(ps, LUA_FAIL);
503
f79faea1
GB
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);
510
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);
d5c004ba
GB
513
514 LUA_RETURN(ps, LUA_OK);
515}
516
517static int a4stats_lua_add_topic(lua_State *ps) {
f79faea1
GB
518 unsigned long channelid, setbyid;
519 const char *topic, *setby;
520
521 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4))
d5c004ba
GB
522 LUA_RETURN(ps, LUA_FAIL);
523
f79faea1
GB
524 channelid = lua_tonumber(ps, 1);
525 topic = lua_tostring(ps, 2);
526 setby = lua_tostring(ps, 3);
527 setbyid = lua_tonumber(ps, 4);
528
529 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
530 "topics", channelid, topic, time(NULL), setby, setbyid);
d5c004ba
GB
531
532 LUA_RETURN(ps, LUA_OK);
533}
534
65c41787
TS
535static int a4stats_lua_db_begin(lua_State *ps) {
536 a4statsdb->query(a4statsdb, NULL, NULL, "BEGIN TRANSACTION", "");
537 LUA_RETURN(ps, LUA_OK);
538}
539
540static int a4stats_lua_db_commit(lua_State *ps) {
541 a4statsdb->query(a4statsdb, NULL, NULL, "COMMIT TRANSACTION", "");
542 LUA_RETURN(ps, LUA_OK);
543}
544
d5c004ba
GB
545static void a4stats_hook_loadscript(int hooknum, void *arg) {
546 void **args = arg;
547 lua_State *l = args[1];
548
5bf3ab9d
GB
549 lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel);
550 lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel);
1b086b45 551 lua_register(l, "a4_set_privacy", a4stats_lua_set_privacy);
f79faea1 552 lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels);
d5c004ba
GB
553 lua_register(l, "a4_add_kick", a4stats_lua_add_kick);
554 lua_register(l, "a4_add_topic", a4stats_lua_add_topic);
92898969 555 lua_register(l, "a4_add_line", a4stats_lua_add_line);
d5c004ba 556 lua_register(l, "a4_update_user", a4stats_lua_update_user);
28d6a377 557 lua_register(l, "a4_update_relation", a4stats_lua_update_relation);
d5c004ba 558 lua_register(l, "a4_escape_string", a4stats_lua_escape_string);
65c41787
TS
559 lua_register(l, "a4_db_begin", a4stats_lua_db_begin);
560 lua_register(l, "a4_db_commit", a4stats_lua_db_commit);
d5c004ba
GB
561}
562
563#define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
564
565static void a4stats_hook_unloadscript(int hooknum, void *arg) {
f79faea1 566 db_callback_info **pnext, *dci;
899f2ee4 567 lua_State *l = arg;
d5c004ba 568
f79faea1
GB
569 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
570 dci = *pnext;
899f2ee4 571 if (dci->interp == l) {
f79faea1
GB
572 *pnext = dci->next;
573 free(dci);
d5c004ba
GB
574 }
575 }
d5c004ba
GB
576}
577
578void _init(void) {
579 lua_list *l;
580 void *args[2];
581
582 a4stats_connectdb();
583
584 registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
585 registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
5905e45f 586 schedulerecurring(time(NULL), 0, CLEANUP_INTERVAL, a4stats_cleanupdb, NULL);
d5c004ba
GB
587
588 args[0] = NULL;
589 for (l = lua_head; l;l = l->next) {
590 args[1] = l->l;
591 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args);
592 }
593}
594
595void _fini(void) {
f79faea1
GB
596 lua_list *l;
597
5905e45f 598 deleteschedule(NULL, a4stats_cleanupdb, NULL);
d5c004ba
GB
599 a4stats_closedb();
600
f79faea1 601 for (l = lua_head; l;l = l->next) {
064db467 602 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT, l->l);
899f2ee4
GB
603
604 lua_unregister(l->l, "a4_enable_channel");
605 lua_unregister(l->l, "a4_disable_channel");
1b086b45 606 lua_unregister(l->l, "a4_set_privacy");
899f2ee4
GB
607 lua_unregister(l->l, "a4_fetch_channels");
608 lua_unregister(l->l, "a4_add_kick");
609 lua_unregister(l->l, "a4_add_topic");
92898969 610 lua_unregister(l->l, "a4_add_line");
899f2ee4
GB
611 lua_unregister(l->l, "a4_fetch_user");
612 lua_unregister(l->l, "a4_update_user");
28d6a377 613 lua_unregister(l->l, "a4_update_relation");
899f2ee4 614 lua_unregister(l->l, "a4_escape_string");
65c41787
TS
615 lua_unregister(l->l, "a4_db_begin");
616 lua_unregister(l->l, "a4_db_commit");
f79faea1
GB
617 }
618
d5c004ba
GB
619 deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
620 deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
621}
622