]>
Commit | Line | Data |
---|---|---|
e7e7312d | 1 | #define _GNU_SOURCE |
d5c004ba GB |
2 | #include <stdio.h> |
3 | #include <stdarg.h> | |
e7e7312d | 4 | #include <string.h> |
d5c004ba GB |
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 | ||
5905e45f | 14 | #define CLEANUP_KEEP 10 /* keep this many topics and kicks per channel around */ |
ef90464f | 15 | #define CLEANUP_INTERVAL 86400 /* db cleanup interval (in seconds) */ |
82645134 | 16 | #define CLEANUP_INACTIVE_DAYS 30 /* disable channels where nothing happened for this many days */ |
5af6ffcf | 17 | #define CLEANUP_DELETE_DAYS 5 /* delete data for channels that have been disabled for this many days */ |
5905e45f | 18 | |
7004a170 | 19 | #define A4STATS_DB_TOLOWER(x) "translate(lower(" x "), '[]\\~', '{}|^')" |
a0b4a0cf TS |
20 | #define A4STATS_DB_EQ_NOCASE(x, y) A4STATS_DB_TOLOWER(x) " = " A4STATS_DB_TOLOWER(y) |
21 | ||
d5c004ba GB |
22 | MODULE_VERSION(""); |
23 | ||
24 | DBAPIConn *a4statsdb; | |
25 | ||
26 | static int a4stats_connectdb(void) { | |
27 | if(!a4statsdb) { | |
cf5ac373 | 28 | a4statsdb = dbapi2open("pqsql", "a4stats"); |
d5c004ba GB |
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, | |
53c0b4a2 | 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, " |
92898969 GB |
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"); | |
d5c004ba | 41 | |
f79faea1 | 42 | a4statsdb->createtable(a4statsdb, NULL, NULL, |
5af6ffcf TS |
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"); | |
f79faea1 | 45 | |
7004a170 CP |
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"); | |
d5c004ba GB |
48 | |
49 | a4statsdb->createtable(a4statsdb, NULL, NULL, | |
5af6ffcf TS |
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"); | |
d5c004ba | 52 | |
7004a170 | 53 | a4statsdb->createtable(a4statsdb, NULL, NULL, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics"); |
d5c004ba GB |
54 | |
55 | a4statsdb->createtable(a4statsdb, NULL, NULL, | |
cf5ac373 | 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, " |
f79faea1 GB |
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, " | |
e0311e96 | 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, " |
d5c004ba | 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, " |
5af6ffcf | 63 | "firstseen INT DEFAULT 0, curnick VARCHAR(16), FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "users", "channels"); |
d5c004ba | 64 | |
7004a170 CP |
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"); | |
d5c004ba | 70 | |
28d6a377 | 71 | a4statsdb->createtable(a4statsdb, NULL, NULL, |
5af6ffcf TS |
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"); | |
28d6a377 | 74 | |
7004a170 CP |
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"); | |
8d6e0508 GB |
77 | |
78 | ||
d5c004ba GB |
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 | ||
f79faea1 GB |
136 | typedef struct db_callback_info { |
137 | struct db_callback_info *next; | |
d5c004ba GB |
138 | |
139 | lua_State *interp; | |
140 | char callback[64]; | |
141 | int uarg_index; | |
f79faea1 GB |
142 | } db_callback_info; |
143 | ||
144 | static db_callback_info *dci_head; | |
145 | ||
146 | static void a4stats_delete_dci(db_callback_info *dci) { | |
5905e45f | 147 | db_callback_info **pnext; |
d5c004ba | 148 | |
f79faea1 GB |
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 | } | |
d5c004ba | 158 | |
d5c004ba GB |
159 | typedef struct user_update_info { |
160 | int stage; | |
161 | char *update; | |
f79faea1 | 162 | unsigned long channelid; |
d5c004ba GB |
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 | ||
e6a4eec1 | 172 | if (uui->stage == 1 || (result != NULL && uui->stage == 3)) |
f79faea1 | 173 | a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account); |
d5c004ba | 174 | else { |
e6a4eec1 | 175 | if (result == NULL || result->affected > 0 || uui->stage == 4) { |
424b0e83 | 176 | if (result == NULL || (result->affected == 0 && uui->stage == 4)) |
d5c004ba GB |
177 | Error("a4stats", ERR_WARNING, "Unable to update user."); |
178 | ||
179 | free(uui->update); | |
d5c004ba GB |
180 | free(uui->account); |
181 | free(uui); | |
316ff6c6 | 182 | goto a4_uuc_return; |
d5c004ba GB |
183 | } |
184 | ||
f79faea1 | 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)); |
d5c004ba | 186 | } |
316ff6c6 TS |
187 | |
188 | a4_uuc_return: | |
189 | if (result) | |
190 | result->clear(result); | |
d5c004ba GB |
191 | } |
192 | ||
193 | static int a4stats_lua_update_user(lua_State *ps) { | |
f79faea1 GB |
194 | const char *account; |
195 | unsigned long channelid, accountid; | |
d5c004ba GB |
196 | char query[4096]; |
197 | int first = 1; | |
198 | user_update_info *uui; | |
199 | ||
f79faea1 | 200 | if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3)) |
d5c004ba GB |
201 | LUA_RETURN(ps, LUA_FAIL); |
202 | ||
f79faea1 | 203 | channelid = lua_tonumber(ps, 1); |
d5c004ba GB |
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 | ||
f79faea1 | 227 | strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)"); |
d5c004ba GB |
228 | |
229 | uui = malloc(sizeof(*uui)); | |
230 | uui->stage = 0; | |
231 | uui->update = strdup(query); | |
f79faea1 | 232 | uui->channelid = channelid; |
d5c004ba GB |
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 | ||
28d6a377 GB |
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); | |
316ff6c6 | 259 | goto a4_urc_return; |
28d6a377 GB |
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)); | |
316ff6c6 | 263 | goto a4_urc_return; |
28d6a377 GB |
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); | |
316ff6c6 TS |
272 | |
273 | a4_urc_return: | |
274 | if (result) | |
275 | result->clear(result); | |
28d6a377 GB |
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 | ||
92898969 GB |
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 | ||
a0b4a0cf | 324 | snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), hour, hour); |
92898969 GB |
325 | |
326 | a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel); | |
327 | ||
328 | LUA_RETURN(ps, LUA_OK); | |
329 | } | |
330 | ||
5af6ffcf TS |
331 | static struct { |
332 | time_t start; | |
82645134 TS |
333 | unsigned long pending; |
334 | unsigned long topicskicks; | |
335 | unsigned long disabled; | |
5af6ffcf | 336 | unsigned long deleted; |
82645134 TS |
337 | } cleanupdata; |
338 | ||
5af6ffcf TS |
339 | static void a4stats_cleanupdb_got_result(void) { |
340 | if (!--cleanupdata.pending) { | |
23c149d9 | 341 | controlwall(NO_OPER, NL_CLEANUP, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.", |
5af6ffcf TS |
342 | cleanupdata.topicskicks, cleanupdata.disabled, cleanupdata.deleted); |
343 | cleanupdata.start = 0; | |
344 | } | |
82645134 TS |
345 | } |
346 | ||
5af6ffcf TS |
347 | static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult *result, void *arg) { |
348 | unsigned long *counter = arg; | |
82645134 | 349 | |
8a12ea21 | 350 | if (result) { |
5af6ffcf | 351 | *counter += result->affected; |
8a12ea21 TS |
352 | result->clear(result); |
353 | } | |
82645134 | 354 | |
5af6ffcf | 355 | a4stats_cleanupdb_got_result(); |
5905e45f TS |
356 | } |
357 | ||
5af6ffcf | 358 | static void a4stats_cleanupdb_cb_active(const struct DBAPIResult *result, void *null) { |
82645134 TS |
359 | unsigned long channelid; |
360 | time_t seen; | |
8a12ea21 TS |
361 | |
362 | if (!result) | |
363 | return; | |
364 | ||
365 | if (result->success) { | |
5905e45f TS |
366 | while (result->next(result)) { |
367 | channelid = strtoul(result->get(result, 0), NULL, 10); | |
82645134 TS |
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 | ||
5af6ffcf TS |
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, | |
82645134 | 377 | "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1", |
5af6ffcf | 378 | "TtU", "channels", cleanupdata.start, channelid); |
82645134 TS |
379 | } else { |
380 | /* cleanup old kicks/topics */ | |
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", "kicks", channelid, "kicks", channelid, (unsigned long)CLEANUP_KEEP); | |
5af6ffcf TS |
386 | cleanupdata.pending++; |
387 | a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks, | |
82645134 TS |
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 | } | |
5905e45f TS |
392 | } |
393 | } | |
8a12ea21 | 394 | result->clear(result); |
5905e45f TS |
395 | } |
396 | ||
397 | static void a4stats_cleanupdb(void *null) { | |
5af6ffcf | 398 | controlwall(NO_OPER, NL_CLEANUP, "Starting a4stats_db cleanup."); |
82645134 | 399 | |
5af6ffcf TS |
400 | if (cleanupdata.start != 0) { |
401 | controlwall(NO_OPER, NL_CLEANUP, "a4stats cleanup already in progress."); | |
402 | return; | |
403 | } | |
5905e45f | 404 | |
5af6ffcf TS |
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, | |
82645134 TS |
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"); | |
5af6ffcf TS |
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)); | |
5905e45f TS |
417 | } |
418 | ||
f79faea1 GB |
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) | |
cf5ac373 | 433 | lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index); |
f79faea1 GB |
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 | ||
5bf3ab9d | 469 | static int a4stats_lua_enable_channel(lua_State *ps) { |
f79faea1 GB |
470 | if (!lua_isstring(ps, 1)) |
471 | LUA_RETURN(ps, LUA_FAIL); | |
472 | ||
899f2ee4 | 473 | a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL)); |
a0b4a0cf | 474 | a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "Ts", "channels", lua_tostring(ps, 1)); |
5bf3ab9d GB |
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 | ||
a0b4a0cf | 483 | a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "Tts", "channels", time(NULL), lua_tostring(ps, 1)); |
f79faea1 GB |
484 | |
485 | LUA_RETURN(ps, LUA_OK); | |
486 | } | |
487 | ||
1b086b45 TS |
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 | ||
a0b4a0cf | 498 | a4statsdb->squery(a4statsdb, "UPDATE ? SET privacy = ? WHERE " A4STATS_DB_EQ_NOCASE("name", "?"), "TUs", "channels", privacy, channel); |
1b086b45 TS |
499 | LUA_RETURN(ps, LUA_OK); |
500 | } | |
501 | ||
d5c004ba | 502 | static int a4stats_lua_add_kick(lua_State *ps) { |
f79faea1 GB |
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)) | |
d5c004ba GB |
507 | LUA_RETURN(ps, LUA_FAIL); |
508 | ||
f79faea1 GB |
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); | |
d5c004ba GB |
518 | |
519 | LUA_RETURN(ps, LUA_OK); | |
520 | } | |
521 | ||
522 | static int a4stats_lua_add_topic(lua_State *ps) { | |
f79faea1 GB |
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)) | |
d5c004ba GB |
527 | LUA_RETURN(ps, LUA_FAIL); |
528 | ||
f79faea1 GB |
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); | |
d5c004ba GB |
536 | |
537 | LUA_RETURN(ps, LUA_OK); | |
538 | } | |
539 | ||
65c41787 TS |
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 | ||
d5c004ba GB |
550 | static void a4stats_hook_loadscript(int hooknum, void *arg) { |
551 | void **args = arg; | |
552 | lua_State *l = args[1]; | |
553 | ||
5bf3ab9d GB |
554 | lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel); |
555 | lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel); | |
1b086b45 | 556 | lua_register(l, "a4_set_privacy", a4stats_lua_set_privacy); |
f79faea1 | 557 | lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels); |
d5c004ba GB |
558 | lua_register(l, "a4_add_kick", a4stats_lua_add_kick); |
559 | lua_register(l, "a4_add_topic", a4stats_lua_add_topic); | |
92898969 | 560 | lua_register(l, "a4_add_line", a4stats_lua_add_line); |
d5c004ba | 561 | lua_register(l, "a4_update_user", a4stats_lua_update_user); |
28d6a377 | 562 | lua_register(l, "a4_update_relation", a4stats_lua_update_relation); |
d5c004ba | 563 | lua_register(l, "a4_escape_string", a4stats_lua_escape_string); |
65c41787 TS |
564 | lua_register(l, "a4_db_begin", a4stats_lua_db_begin); |
565 | lua_register(l, "a4_db_commit", a4stats_lua_db_commit); | |
d5c004ba GB |
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) { | |
f79faea1 | 571 | db_callback_info **pnext, *dci; |
899f2ee4 | 572 | lua_State *l = arg; |
d5c004ba | 573 | |
f79faea1 GB |
574 | for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) { |
575 | dci = *pnext; | |
899f2ee4 | 576 | if (dci->interp == l) { |
f79faea1 GB |
577 | *pnext = dci->next; |
578 | free(dci); | |
d5c004ba GB |
579 | } |
580 | } | |
d5c004ba GB |
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); | |
5905e45f | 591 | schedulerecurring(time(NULL), 0, CLEANUP_INTERVAL, a4stats_cleanupdb, NULL); |
d5c004ba GB |
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) { | |
f79faea1 GB |
601 | lua_list *l; |
602 | ||
5905e45f | 603 | deleteschedule(NULL, a4stats_cleanupdb, NULL); |
d5c004ba GB |
604 | a4stats_closedb(); |
605 | ||
f79faea1 | 606 | for (l = lua_head; l;l = l->next) { |
064db467 | 607 | a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT, l->l); |
899f2ee4 GB |
608 | |
609 | lua_unregister(l->l, "a4_enable_channel"); | |
610 | lua_unregister(l->l, "a4_disable_channel"); | |
1b086b45 | 611 | lua_unregister(l->l, "a4_set_privacy"); |
899f2ee4 GB |
612 | lua_unregister(l->l, "a4_fetch_channels"); |
613 | lua_unregister(l->l, "a4_add_kick"); | |
614 | lua_unregister(l->l, "a4_add_topic"); | |
92898969 | 615 | lua_unregister(l->l, "a4_add_line"); |
899f2ee4 GB |
616 | lua_unregister(l->l, "a4_fetch_user"); |
617 | lua_unregister(l->l, "a4_update_user"); | |
28d6a377 | 618 | lua_unregister(l->l, "a4_update_relation"); |
899f2ee4 | 619 | lua_unregister(l->l, "a4_escape_string"); |
65c41787 TS |
620 | lua_unregister(l->l, "a4_db_begin"); |
621 | lua_unregister(l->l, "a4_db_commit"); | |
f79faea1 GB |
622 | } |
623 | ||
d5c004ba GB |
624 | deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript); |
625 | deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript); | |
626 | } | |
627 |