]> jfr.im git - irc/quakenet/newserv.git/blob - a4stats/a4stats_db.c
A4STATS: remove E style escapes and switch to createtable for indices
[irc/quakenet/newserv.git] / a4stats / a4stats_db.c
1 #define _GNU_SOURCE
2 #include <stdio.h>
3 #include <stdarg.h>
4 #include <string.h>
5 #include "../lib/version.h"
6 #include "../dbapi2/dbapi2.h"
7 #include "../core/error.h"
8 #include "../core/hooks.h"
9 #include "../core/schedule.h"
10 #include "../control/control.h"
11 #include "../irc/irc.h"
12 #include "../lua/lua.h"
13
14 #define CLEANUP_KEEP 10 /* keep this many topics and kicks per channel around */
15 #define CLEANUP_INTERVAL 86400 /* db cleanup interval (in seconds) */
16 #define CLEANUP_INACTIVE_DAYS 30 /* disable channels where nothing happened for this many days */
17 #define CLEANUP_DELETE_DAYS 5 /* delete data for channels that have been disabled for this many days */
18
19 #define A4STATS_DB_TOLOWER(x) "translate(lower(" x "), '[]\\~', '{}|^')"
20 #define A4STATS_DB_EQ_NOCASE(x, y) A4STATS_DB_TOLOWER(x) " = " A4STATS_DB_TOLOWER(y)
21
22 MODULE_VERSION("");
23
24 DBAPIConn *a4statsdb;
25
26 static int a4stats_connectdb(void) {
27 if(!a4statsdb) {
28 a4statsdb = dbapi2open("pqsql", "a4stats");
29 if(!a4statsdb) {
30 Error("a4stats", ERR_WARNING, "Unable to connect to db -- not loaded.");
31 return 0;
32 }
33 }
34
35 a4statsdb->createtable(a4statsdb, NULL, NULL,
36 "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, "
37 "h0 INT DEFAULT 0, h1 INT DEFAULT 0, h2 INT DEFAULT 0, h3 INT DEFAULT 0, h4 INT DEFAULT 0, h5 INT DEFAULT 0, "
38 "h6 INT DEFAULT 0, h7 INT DEFAULT 0, h8 INT DEFAULT 0, h9 INT DEFAULT 0, h10 INT DEFAULT 0, h11 INT DEFAULT 0, "
39 "h12 INT DEFAULT 0, h13 INT DEFAULT 0, h14 INT DEFAULT 0, h15 INT DEFAULT 0, h16 INT DEFAULT 0, h17 INT DEFAULT 0, "
40 "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");
41
42 a4statsdb->createtable(a4statsdb, NULL, NULL,
43 "CREATE TABLE ? (channelid INT, kicker VARCHAR(128), kickerid INT, victim VARCHAR(128), victimid INT, timestamp INT, reason VARCHAR(256),"
44 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "kicks", "channels");
45
46 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX kicks_channelid_index ON ? (channelid)", "T", "kicks");
47 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX kicks_timestamp_index ON ? (timestamp)", "T", "kicks");
48
49 a4statsdb->createtable(a4statsdb, NULL, NULL,
50 "CREATE TABLE ? (channelid INT, setby VARCHAR(128), setbyid INT, timestamp INT, topic VARCHAR(512),"
51 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "topics", "channels");
52
53 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics");
54
55 a4statsdb->createtable(a4statsdb, NULL, NULL,
56 "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, "
57 "h0 INT DEFAULT 0, h1 INT DEFAULT 0, h2 INT DEFAULT 0, h3 INT DEFAULT 0, h4 INT DEFAULT 0, h5 INT DEFAULT 0, "
58 "h6 INT DEFAULT 0, h7 INT DEFAULT 0, h8 INT DEFAULT 0, h9 INT DEFAULT 0, h10 INT DEFAULT 0, h11 INT DEFAULT 0, "
59 "h12 INT DEFAULT 0, h13 INT DEFAULT 0, h14 INT DEFAULT 0, h15 INT DEFAULT 0, h16 INT DEFAULT 0, h17 INT DEFAULT 0, "
60 "h18 INT DEFAULT 0, h19 INT DEFAULT 0, h20 INT DEFAULT 0, h21 INT DEFAULT 0, h22 INT DEFAULT 0, h23 INT DEFAULT 0, "
61 "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, "
62 "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, "
63 "firstseen INT DEFAULT 0, curnick VARCHAR(16), FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "users", "channels");
64
65 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX users_account_index ON ? (account)", "T", "users");
66 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX users_accountid_index ON ? (accountid)", "T", "users");
67 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX users_channelid_index ON ? (channelid)", "T", "users");
68 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE UNIQUE INDEX users_channelid_account_accountid_index ON ? (channelid, account, accountid)", "T", "users");
69 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX users_channelid_lines_index ON ? (channelid, lines)", "T", "users");
70
71 a4statsdb->createtable(a4statsdb, NULL, NULL,
72 "CREATE TABLE ? (channelid INT, first VARCHAR(128), firstid INT, second VARCHAR(128), secondid INT, seen INT, score INT DEFAULT 1,"
73 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "relations", "channels");
74
75 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX relations_channelid_index ON ? (channelid)", "T", "relations");
76 a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX relations_score_index ON ? (score)", "T", "relations");
77
78
79 return 1;
80 }
81
82 static void a4stats_closedb(void) {
83 if(!a4statsdb)
84 return;
85
86 a4statsdb->close(a4statsdb);
87 a4statsdb = NULL;
88 }
89
90 static int a4stats_lua_escape_string(lua_State *ps) {
91 const char *input;
92 char *buf, *buf2;
93 size_t len, i, o;
94
95 if (!lua_isstring(ps, 1))
96 LUA_RETURN(ps, LUA_FAIL);
97
98 input = lua_tostring(ps, 1);
99 len = strlen(input);
100
101 buf = malloc(len * 2 + 1);
102
103 if (!buf)
104 LUA_RETURN(ps, LUA_FAIL);
105
106 a4statsdb->escapestring(a4statsdb, buf, input, len);
107
108 buf2 = malloc(len * 4 + 1);
109
110 if (!buf2) {
111 free(buf);
112 LUA_RETURN(ps, LUA_FAIL);
113 }
114
115 /* escape "?" */
116 o = 0;
117 for (i = 0; buf[i]; i++) {
118 if (buf[i] == '?') {
119 buf2[i + o] = '\\';
120 o++;
121 }
122
123 buf2[i + o] = buf[i];
124 }
125 buf2[i + o] = '\0';
126
127 free(buf);
128
129 lua_pushstring(ps, buf2);
130
131 free(buf2);
132
133 return 1;
134 }
135
136 typedef struct db_callback_info {
137 struct db_callback_info *next;
138
139 lua_State *interp;
140 char callback[64];
141 int uarg_index;
142 } db_callback_info;
143
144 static db_callback_info *dci_head;
145
146 static void a4stats_delete_dci(db_callback_info *dci) {
147 db_callback_info **pnext;
148
149 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
150 if (*pnext == dci) {
151 *pnext = dci->next;
152 break;
153 }
154 }
155
156 free(dci);
157 }
158
159 typedef struct user_update_info {
160 int stage;
161 char *update;
162 unsigned long channelid;
163 char *account;
164 unsigned long accountid;
165 } user_update_info;
166
167 static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) {
168 user_update_info *uui = uarg;
169
170 uui->stage++;
171
172 if (uui->stage == 1 || (result != NULL && uui->stage == 3))
173 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account);
174 else {
175 if (result == NULL || result->affected > 0 || uui->stage == 4) {
176 if (result == NULL || (result->affected == 0 && uui->stage == 4))
177 Error("a4stats", ERR_WARNING, "Unable to update user.");
178
179 free(uui->update);
180 free(uui->account);
181 free(uui);
182 goto a4_uuc_return;
183 }
184
185 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));
186 }
187
188 a4_uuc_return:
189 if (result)
190 result->clear(result);
191 }
192
193 static int a4stats_lua_update_user(lua_State *ps) {
194 const char *account;
195 unsigned long channelid, accountid;
196 char query[4096];
197 int first = 1;
198 user_update_info *uui;
199
200 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3))
201 LUA_RETURN(ps, LUA_FAIL);
202
203 channelid = lua_tonumber(ps, 1);
204 account = lua_tostring(ps, 2);
205 accountid = lua_tonumber(ps, 3);
206
207 strcpy(query, "UPDATE ? SET ");
208
209 lua_pushvalue(ps, 4);
210 lua_pushnil(ps);
211
212 while (lua_next(ps, -2)) {
213 const char *value = lua_tostring(ps, -1);
214
215 if (first)
216 first = 0;
217 else
218 strcat(query, ", ");
219
220 strcat(query, value);
221
222 lua_pop(ps, 1);
223 }
224
225 lua_pop(ps, 1);
226
227 strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
228
229 uui = malloc(sizeof(*uui));
230 uui->stage = 0;
231 uui->update = strdup(query);
232 uui->channelid = channelid;
233 uui->account = strdup(account);
234 uui->accountid = accountid;
235
236 a4stats_update_user_cb(NULL, uui);
237
238 LUA_RETURN(ps, LUA_OK);
239 }
240
241 typedef struct relation_update_info {
242 int stage;
243 unsigned long channelid;
244 char *first;
245 unsigned long firstid;
246 char *second;
247 unsigned long secondid;
248 } relation_update_info;
249
250 static void a4stats_update_relation_cb(const struct DBAPIResult *result, void *uarg) {
251 relation_update_info *rui = uarg;
252
253 rui->stage++;
254
255 if (rui->stage == 1) {
256 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "UPDATE ? SET score = score + 1, seen = ? "
257 "WHERE channelid = ? AND first = ? AND firstid = ? AND second = ? AND secondid = ?",
258 "TtUsUsU", "relations", time(NULL), rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid);
259 goto a4_urc_return;
260 } else if (rui->stage == 2 && result && result->affected == 0) {
261 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "INSERT INTO ? (channelid, first, firstid, second, secondid, seen) VALUES (?, ?, ?, ?, ?, ?)",
262 "TUsUsUt", "relations", rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid, time(NULL));
263 goto a4_urc_return;
264 }
265
266 if (!result || result->affected == 0)
267 Error("a4stats", ERR_WARNING, "Unable to update relation.");
268
269 free(rui->first);
270 free(rui->second);
271 free(rui);
272
273 a4_urc_return:
274 if (result)
275 result->clear(result);
276 }
277
278 static int a4stats_lua_update_relation(lua_State *ps) {
279 const char *user1, *user2;
280 unsigned long channelid, user1id, user2id;
281 relation_update_info *rui;
282
283 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4) || !lua_isnumber(ps, 5))
284 LUA_RETURN(ps, LUA_FAIL);
285
286 channelid = lua_tonumber(ps, 1);
287 user1 = lua_tostring(ps, 2);
288 user1id = lua_tonumber(ps, 3);
289 user2 = lua_tostring(ps, 4);
290 user2id = lua_tonumber(ps, 5);
291
292 rui = malloc(sizeof(*rui));
293 rui->stage = 0;
294 rui->channelid = channelid;
295
296 if (user1id < user2id || (user1id == user2id && strcmp(user1, user2) <= 0)) {
297 rui->first = strdup(user1);
298 rui->firstid = user1id;
299 rui->second = strdup(user2);
300 rui->secondid = user2id;
301 } else {
302 rui->first = strdup(user2);
303 rui->firstid = user2id;
304 rui->second = strdup(user1);
305 rui->secondid = user1id;
306 }
307
308 a4stats_update_relation_cb(NULL, rui);
309
310 LUA_RETURN(ps, LUA_OK);
311 }
312
313 static int a4stats_lua_add_line(lua_State *ps) {
314 char query[256];
315 const char *channel;
316 int hour;
317
318 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
319 LUA_RETURN(ps, LUA_FAIL);
320
321 channel = lua_tostring(ps, 1);
322 hour = lua_tonumber(ps, 2);
323
324 snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), hour, hour);
325
326 a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel);
327
328 LUA_RETURN(ps, LUA_OK);
329 }
330
331 static struct {
332 time_t start;
333 unsigned long pending;
334 unsigned long topicskicks;
335 unsigned long disabled;
336 unsigned long deleted;
337 } cleanupdata;
338
339 static void a4stats_cleanupdb_got_result(void) {
340 if (!--cleanupdata.pending) {
341 controlwall(NO_OPER, NL_CLEANUP, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.",
342 cleanupdata.topicskicks, cleanupdata.disabled, cleanupdata.deleted);
343 cleanupdata.start = 0;
344 }
345 }
346
347 static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult *result, void *arg) {
348 unsigned long *counter = arg;
349
350 if (result) {
351 *counter += result->affected;
352 result->clear(result);
353 }
354
355 a4stats_cleanupdb_got_result();
356 }
357
358 static void a4stats_cleanupdb_cb_active(const struct DBAPIResult *result, void *null) {
359 unsigned long channelid;
360 time_t seen;
361
362 if (!result)
363 return;
364
365 if (result->success) {
366 while (result->next(result)) {
367 channelid = strtoul(result->get(result, 0), NULL, 10);
368 seen = (time_t)strtoul(result->get(result, 2), NULL, 10);
369 /* use channel enabling timestamp if there was never any event */
370 if (!seen)
371 seen = (time_t)strtoul(result->get(result, 1), NULL, 10);
372
373 if (seen < cleanupdata.start - CLEANUP_INACTIVE_DAYS * 86400) {
374 /* disable inactive channels */
375 cleanupdata.pending++;
376 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.disabled,
377 "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1",
378 "TtU", "channels", cleanupdata.start, channelid);
379 } else {
380 /* cleanup old kicks/topics */
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", "kicks", channelid, "kicks", channelid, (unsigned long)CLEANUP_KEEP);
386 cleanupdata.pending++;
387 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
388 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
389 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
390 "TUTUU", "topics", channelid, "topics", channelid, (unsigned long)CLEANUP_KEEP);
391 }
392 }
393 }
394 result->clear(result);
395 }
396
397 static void a4stats_cleanupdb(void *null) {
398 controlwall(NO_OPER, NL_CLEANUP, "Starting a4stats_db cleanup.");
399
400 if (cleanupdata.start != 0) {
401 controlwall(NO_OPER, NL_CLEANUP, "a4stats cleanup already in progress.");
402 return;
403 }
404
405 cleanupdata.start = time(NULL);
406 cleanupdata.pending = 0;
407 cleanupdata.topicskicks = 0;
408 cleanupdata.disabled = 0;
409 cleanupdata.deleted = 0;
410
411 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_active, NULL,
412 "SELECT id, timestamp, MAX(users.seen) FROM ? LEFT JOIN ? AS users ON id = users.channelid WHERE active = 1 GROUP BY id",
413 "TT", "channels", "users");
414 cleanupdata.pending++;
415 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.deleted,
416 "DELETE FROM ? WHERE active = 0 AND deleted < ?", "Tt", "channels", (time_t)(cleanupdata.start - CLEANUP_DELETE_DAYS * 84600));
417 }
418
419 static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) {
420 db_callback_info *dci = uarg;
421 unsigned long channelid;
422 int active;
423 char *channel;
424
425 if (result) {
426 if (result->success) {
427 while (result->next(result)) {
428 channelid = strtoul(result->get(result, 0), NULL, 10);
429 channel = result->get(result, 1);
430 active = atoi(result->get(result, 2));
431
432 if (dci->interp)
433 lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index);
434 }
435 }
436
437 result->clear(result);
438 }
439
440 if (dci->interp)
441 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
442
443 a4stats_delete_dci(dci);
444 }
445
446 static int a4stats_lua_fetch_channels(lua_State *ps) {
447 const char *callback;
448 db_callback_info *dci;
449
450 if (!lua_isstring(ps, 1))
451 LUA_RETURN(ps, LUA_FAIL);
452
453 callback = lua_tostring(ps, 1);
454
455 dci = malloc(sizeof(*dci));
456 dci->interp = ps;
457
458 strncpy(dci->callback, callback, sizeof(dci->callback));
459 dci->callback[sizeof(dci->callback) - 1] = '\0';
460
461 lua_pushvalue(ps, 2);
462 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
463
464 a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels");
465
466 LUA_RETURN(ps, LUA_OK);
467 }
468
469 static int a4stats_lua_enable_channel(lua_State *ps) {
470 if (!lua_isstring(ps, 1))
471 LUA_RETURN(ps, LUA_FAIL);
472
473 a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL));
474 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "Ts", "channels", lua_tostring(ps, 1));
475
476 LUA_RETURN(ps, LUA_OK);
477 }
478
479 static int a4stats_lua_disable_channel(lua_State *ps) {
480 if (!lua_isstring(ps, 1))
481 LUA_RETURN(ps, LUA_FAIL);
482
483 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "Tts", "channels", time(NULL), lua_tostring(ps, 1));
484
485 LUA_RETURN(ps, LUA_OK);
486 }
487
488 static int a4stats_lua_set_privacy(lua_State *ps) {
489 const char *channel;
490 unsigned long privacy;
491
492 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
493 LUA_RETURN(ps, LUA_FAIL);
494
495 channel = lua_tostring(ps, 1);
496 privacy = lua_tonumber(ps, 2);
497
498 a4statsdb->squery(a4statsdb, "UPDATE ? SET privacy = ? WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "TUs", "channels", privacy, channel);
499 LUA_RETURN(ps, LUA_OK);
500 }
501
502 static int a4stats_lua_add_kick(lua_State *ps) {
503 unsigned long channelid, kickerid, victimid;
504 const char *kicker, *victim, *reason;
505
506 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))
507 LUA_RETURN(ps, LUA_FAIL);
508
509 channelid = lua_tonumber(ps, 1);
510 kicker = lua_tostring(ps, 2);
511 kickerid = lua_tonumber(ps, 3);
512 victim = lua_tostring(ps, 4);
513 victimid = lua_tonumber(ps, 5);
514 reason = lua_tostring(ps, 6);
515
516 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, kicker, kickerid, victim, victimid, timestamp, reason) VALUES (?, ?, ?, ?, ?, ?, ?)", "TUsUsUts",
517 "kicks", channelid, kicker, kickerid, victim, victimid, time(NULL), reason);
518
519 LUA_RETURN(ps, LUA_OK);
520 }
521
522 static int a4stats_lua_add_topic(lua_State *ps) {
523 unsigned long channelid, setbyid;
524 const char *topic, *setby;
525
526 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4))
527 LUA_RETURN(ps, LUA_FAIL);
528
529 channelid = lua_tonumber(ps, 1);
530 topic = lua_tostring(ps, 2);
531 setby = lua_tostring(ps, 3);
532 setbyid = lua_tonumber(ps, 4);
533
534 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
535 "topics", channelid, topic, time(NULL), setby, setbyid);
536
537 LUA_RETURN(ps, LUA_OK);
538 }
539
540 static int a4stats_lua_db_begin(lua_State *ps) {
541 a4statsdb->query(a4statsdb, NULL, NULL, "BEGIN TRANSACTION", "");
542 LUA_RETURN(ps, LUA_OK);
543 }
544
545 static int a4stats_lua_db_commit(lua_State *ps) {
546 a4statsdb->query(a4statsdb, NULL, NULL, "COMMIT TRANSACTION", "");
547 LUA_RETURN(ps, LUA_OK);
548 }
549
550 static void a4stats_hook_loadscript(int hooknum, void *arg) {
551 void **args = arg;
552 lua_State *l = args[1];
553
554 lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel);
555 lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel);
556 lua_register(l, "a4_set_privacy", a4stats_lua_set_privacy);
557 lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels);
558 lua_register(l, "a4_add_kick", a4stats_lua_add_kick);
559 lua_register(l, "a4_add_topic", a4stats_lua_add_topic);
560 lua_register(l, "a4_add_line", a4stats_lua_add_line);
561 lua_register(l, "a4_update_user", a4stats_lua_update_user);
562 lua_register(l, "a4_update_relation", a4stats_lua_update_relation);
563 lua_register(l, "a4_escape_string", a4stats_lua_escape_string);
564 lua_register(l, "a4_db_begin", a4stats_lua_db_begin);
565 lua_register(l, "a4_db_commit", a4stats_lua_db_commit);
566 }
567
568 #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
569
570 static void a4stats_hook_unloadscript(int hooknum, void *arg) {
571 db_callback_info **pnext, *dci;
572 lua_State *l = arg;
573
574 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
575 dci = *pnext;
576 if (dci->interp == l) {
577 *pnext = dci->next;
578 free(dci);
579 }
580 }
581 }
582
583 void _init(void) {
584 lua_list *l;
585 void *args[2];
586
587 a4stats_connectdb();
588
589 registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
590 registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
591 schedulerecurring(time(NULL), 0, CLEANUP_INTERVAL, a4stats_cleanupdb, NULL);
592
593 args[0] = NULL;
594 for (l = lua_head; l;l = l->next) {
595 args[1] = l->l;
596 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args);
597 }
598 }
599
600 void _fini(void) {
601 lua_list *l;
602
603 deleteschedule(NULL, a4stats_cleanupdb, NULL);
604 a4stats_closedb();
605
606 for (l = lua_head; l;l = l->next) {
607 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT, l->l);
608
609 lua_unregister(l->l, "a4_enable_channel");
610 lua_unregister(l->l, "a4_disable_channel");
611 lua_unregister(l->l, "a4_set_privacy");
612 lua_unregister(l->l, "a4_fetch_channels");
613 lua_unregister(l->l, "a4_add_kick");
614 lua_unregister(l->l, "a4_add_topic");
615 lua_unregister(l->l, "a4_add_line");
616 lua_unregister(l->l, "a4_fetch_user");
617 lua_unregister(l->l, "a4_update_user");
618 lua_unregister(l->l, "a4_update_relation");
619 lua_unregister(l->l, "a4_escape_string");
620 lua_unregister(l->l, "a4_db_begin");
621 lua_unregister(l->l, "a4_db_commit");
622 }
623
624 deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
625 deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
626 }
627