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