Crossfire Server, Branch 1.12
R12190
|
00001 /*****************************************************************************/ 00002 /* Logger plugin version 1.0 alpha. */ 00003 /* Contact: */ 00004 /*****************************************************************************/ 00005 /* That code is placed under the GNU General Public Licence (GPL) */ 00006 /* (C)2007 by Weeger Nicolas (Feel free to deliver your complaints) */ 00007 /*****************************************************************************/ 00008 /* CrossFire, A Multiplayer game for X-windows */ 00009 /* */ 00010 /* Copyright (C) 2000 Mark Wedel */ 00011 /* Copyright (C) 1992 Frank Tore Johansen */ 00012 /* */ 00013 /* This program is free software; you can redistribute it and/or modify */ 00014 /* it under the terms of the GNU General Public License as published by */ 00015 /* the Free Software Foundation; either version 2 of the License, or */ 00016 /* (at your option) any later version. */ 00017 /* */ 00018 /* This program is distributed in the hope that it will be useful, */ 00019 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 00020 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 00021 /* GNU General Public License for more details. */ 00022 /* */ 00023 /* You should have received a copy of the GNU General Public License */ 00024 /* along with this program; if not, write to the Free Software */ 00025 /* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ 00026 /* */ 00027 /*****************************************************************************/ 00028 00048 #include <cflogger.h> 00049 #ifndef __CEXTRACT__ 00050 #include <cflogger_proto.h> 00051 #endif 00052 /*#include <stdarg.h>*/ 00053 00054 #include <sqlite3.h> 00055 00057 #define CFLOGGER_CURRENT_FORMAT 3 00058 00060 static sqlite3 *database; 00061 00063 static int last_stored_day = -1; 00064 00080 static int check_tables_callback(void *param, int argc, char **argv, char **azColName) { 00081 int *format = (int *)param; 00082 00083 *format = atoi(argv[0]); 00084 return 0; 00085 } 00086 00102 static int do_sql(const char *sql) { 00103 int err; 00104 char *msg; 00105 00106 if (!database) 00107 return -1; 00108 00109 err = sqlite3_exec(database, sql, NULL, NULL, &msg); 00110 if (err != SQLITE_OK) { 00111 cf_log(llevError, " [%s] error: %d [%s] for sql = %s\n", PLUGIN_NAME, err, msg, sql); 00112 sqlite3_free(msg); 00113 } 00114 return err; 00115 } 00116 00143 static int update_table_format(const char *table, const char *newschema, 00144 const char *select_columns) { 00145 char *sql; 00146 int err; 00147 00148 sql = sqlite3_mprintf("ALTER TABLE %s RENAME TO %s_old;", table, table); 00149 err = do_sql(sql); 00150 sqlite3_free(sql); 00151 if (err != SQLITE_OK) 00152 return err; 00153 00154 sql = sqlite3_mprintf("CREATE TABLE %s(%s);", table, newschema); 00155 err = do_sql(sql); 00156 sqlite3_free(sql); 00157 if (err != SQLITE_OK) 00158 return err; 00159 00160 sql = sqlite3_mprintf("INSERT INTO %s SELECT %s FROM %s_old;", 00161 table, select_columns, table); 00162 err = do_sql(sql); 00163 sqlite3_free(sql); 00164 if (err != SQLITE_OK) 00165 return err; 00166 00167 sql = sqlite3_mprintf("DROP TABLE %s_old;", table, table); 00168 err = do_sql(sql); 00169 sqlite3_free(sql); 00170 /* Final return. */ 00171 return err; 00172 } 00173 00180 #define DO_OR_ROLLBACK(sqlstring) \ 00181 if (do_sql(sqlstring) != SQLITE_OK) { \ 00182 do_sql("rollback transaction;"); \ 00183 cf_log(llevError, " [%s] Logger database format update failed! Couldn't upgrade from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);\ 00184 sqlite3_close(database); \ 00185 database = NULL; \ 00186 return; \ 00187 } 00188 00189 #define UPDATE_OR_ROLLBACK(tbl, newschema, select_columns) \ 00190 if (update_table_format((tbl), (newschema), (select_columns)) != SQLITE_OK) { \ 00191 do_sql("rollback transaction;"); \ 00192 cf_log(llevError, " [%s] Logger database format update failed! Couldn't upgrade from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);\ 00193 sqlite3_close(database); \ 00194 database = NULL; \ 00195 return; \ 00196 } 00197 00201 static void check_tables(void) { 00202 int format; 00203 int err; 00204 format = 0; 00205 00206 err = sqlite3_exec(database, "select param_value from parameters where param_name = 'version';", check_tables_callback, &format, NULL); 00207 00208 /* Safety check. */ 00209 if (format > CFLOGGER_CURRENT_FORMAT) { 00210 cf_log(llevError, " [%s] Logger database format (%d) is newer than supported (%d) by this binary!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT); 00211 /* This will disable using the db since do_sql() checks if database is 00212 * NULL. 00213 */ 00214 sqlite3_close(database); 00215 database = NULL; 00216 } 00217 00218 /* Check if we need to upgrade/create database. */ 00219 if (format < 1) { 00220 cf_log(llevDebug, " [%s] Creating logger database schema (format 1).\n", PLUGIN_NAME); 00221 if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) { 00222 cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT); 00223 sqlite3_close(database); 00224 database = NULL; 00225 return; 00226 } 00227 DO_OR_ROLLBACK("create table living(liv_id integer primary key autoincrement, liv_name text, liv_is_player integer, liv_level integer);"); 00228 DO_OR_ROLLBACK("create table region(reg_id integer primary key autoincrement, reg_name text);"); 00229 DO_OR_ROLLBACK("create table map(map_id integer primary key autoincrement, map_path text, map_reg_id integer);"); 00230 DO_OR_ROLLBACK("create table time(time_real integer, time_ingame text);"); 00231 00232 DO_OR_ROLLBACK("create table living_event(le_liv_id integer, le_time integer, le_code integer, le_map_id integer);"); 00233 DO_OR_ROLLBACK("create table map_event(me_map_id integer, me_time integer, me_code integer, me_living_id integer);"); 00234 DO_OR_ROLLBACK("create table kill_event(ke_time integer, ke_victim_id integer, ke_victim_level integer, ke_map_id integer , ke_killer_id integer, ke_killer_level integer);"); 00235 00236 DO_OR_ROLLBACK("create table parameters(param_name text, param_value text);"); 00237 DO_OR_ROLLBACK("insert into parameters values( 'version', '1' );"); 00238 do_sql("COMMIT TRANSACTION;"); 00239 } 00240 00241 /* Must be able to handle update from format 1. If we are creating a new 00242 * database, format 1 is still created first, then updated. 00243 * 00244 * This way is simpler than having to create two ways to make a format 2 db. 00245 */ 00246 if (format < 2) { 00247 cf_log(llevDebug, " [%s] Upgrading logger database schema (to format 2).\n", PLUGIN_NAME); 00248 if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) { 00249 cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT); 00250 sqlite3_close(database); 00251 database = NULL; 00252 return; 00253 } 00254 /* Update schema for various tables. Why so complex? Because ALTER TABLE 00255 * can't add the "primary key" bit or other constraints... 00256 */ 00257 UPDATE_OR_ROLLBACK("living", "liv_id INTEGER PRIMARY KEY AUTOINCREMENT, liv_name TEXT NOT NULL, liv_is_player INTEGER NOT NULL, liv_level INTEGER NOT NULL", "*"); 00258 UPDATE_OR_ROLLBACK("region", "reg_id INTEGER PRIMARY KEY AUTOINCREMENT, reg_name TEXT UNIQUE NOT NULL", "*"); 00259 UPDATE_OR_ROLLBACK("map", "map_id INTEGER PRIMARY KEY AUTOINCREMENT, map_path TEXT NOT NULL, map_reg_id INTEGER NOT NULL, CONSTRAINT map_path_reg_id UNIQUE(map_path, map_reg_id)", "*"); 00260 #if 0 00261 /* Turned out this was incorrect. And version 1 -> 3 directly works for this. */ 00262 UPDATE_OR_ROLLBACK("time", "time_real INTEGER PRIMARY KEY, time_ingame TEXT UNIQUE NOT NULL"); 00263 #endif 00264 UPDATE_OR_ROLLBACK("living_event", "le_liv_id INTEGER NOT NULL, le_time INTEGER NOT NULL, le_code INTEGER NOT NULL, le_map_id INTEGER NOT NULL", "*"); 00265 UPDATE_OR_ROLLBACK("map_event", "me_map_id INTEGER NOT NULL, me_time INTEGER NOT NULL, me_code INTEGER NOT NULL, me_living_id INTEGER NOT NULL", "*"); 00266 UPDATE_OR_ROLLBACK("kill_event", "ke_time INTEGER NOT NULL, ke_victim_id INTEGER NOT NULL, ke_victim_level INTEGER NOT NULL, ke_map_id INTEGER NOT NULL, ke_killer_id INTEGER NOT NULL, ke_killer_level INTEGER NOT NULL", "*"); 00267 00268 /* Handle changed parameters table format: */ 00269 /* Due to backward compatiblity "primary key" in SQLite doesn't imply 00270 * "not null" (http://www.sqlite.org/lang_createtable.html), unless it 00271 * is "integer primary key". 00272 * 00273 * We don't need to save anything stored in this in format 1, it was 00274 * only used for storing what format was used. 00275 */ 00276 DO_OR_ROLLBACK("DROP TABLE parameters;"); 00277 DO_OR_ROLLBACK("CREATE TABLE parameters(param_name TEXT NOT NULL PRIMARY KEY, param_value TEXT);"); 00278 DO_OR_ROLLBACK("INSERT INTO parameters (param_name, param_value) VALUES( 'version', '2' );"); 00279 00280 /* Create various indexes. */ 00281 DO_OR_ROLLBACK("CREATE INDEX living_name_player_level ON living(liv_name,liv_is_player,liv_level);"); 00282 00283 /* Newspaper module could make use of some indexes too: */ 00284 DO_OR_ROLLBACK("CREATE INDEX kill_event_time ON kill_event(ke_time);"); 00285 DO_OR_ROLLBACK("CREATE INDEX map_reg_id ON map(map_reg_id);"); 00286 00287 /* Finally commit the transaction. */ 00288 do_sql("COMMIT TRANSACTION;"); 00289 } 00290 00291 if (format < 3) { 00292 cf_log(llevDebug, " [%s] Upgrading logger database schema (to format 3).\n", PLUGIN_NAME); 00293 if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) { 00294 cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT); 00295 sqlite3_close(database); 00296 database = NULL; 00297 return; 00298 } 00299 UPDATE_OR_ROLLBACK("time", "time_ingame TEXT NOT NULL PRIMARY KEY, time_real INTEGER NOT NULL", "time_ingame, time_real"); 00300 DO_OR_ROLLBACK("UPDATE parameters SET param_value = '3' WHERE param_name = 'version';"); 00301 do_sql("COMMIT TRANSACTION;"); 00302 /* After all these changes better vacuum... The tables could have been 00303 * huge, and since we recreated several of them above there could be a 00304 * lot of wasted space. 00305 */ 00306 do_sql("VACUUM;"); 00307 } 00308 } 00309 00325 static int get_living_id(object *living) { 00326 char **line; 00327 char *sql; 00328 int nrow, ncolumn, id; 00329 00330 if (living->type == PLAYER) 00331 sql = sqlite3_mprintf("select liv_id from living where liv_name='%q' and liv_is_player = 1", living->name); 00332 else 00333 sql = sqlite3_mprintf("select liv_id from living where liv_name='%q' and liv_is_player = 0 and liv_level = %d", living->name, living->level); 00334 sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL); 00335 00336 if (nrow > 0) 00337 id = atoi(line[ncolumn]); 00338 else { 00339 sqlite3_free(sql); 00340 sql = sqlite3_mprintf("insert into living(liv_name, liv_is_player, liv_level) values('%q', %d, %d)", living->name, living->type == PLAYER ? 1 : 0, living->level); 00341 do_sql(sql); 00342 id = sqlite3_last_insert_rowid(database); 00343 } 00344 sqlite3_free(sql); 00345 sqlite3_free_table(line); 00346 return id; 00347 } 00348 00359 static int get_region_id(region *reg) { 00360 char **line; 00361 char *sql; 00362 int nrow, ncolumn, id; 00363 00364 if (!reg) 00365 return 0; 00366 00367 sql = sqlite3_mprintf("select reg_id from region where reg_name='%q'", reg->name); 00368 sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL); 00369 00370 if (nrow > 0) 00371 id = atoi(line[ncolumn]); 00372 else { 00373 sqlite3_free(sql); 00374 sql = sqlite3_mprintf("insert into region(reg_name) values( '%q' )", reg->name); 00375 do_sql(sql); 00376 id = sqlite3_last_insert_rowid(database); 00377 } 00378 sqlite3_free(sql); 00379 sqlite3_free_table(line); 00380 return id; 00381 } 00382 00395 static int get_map_id(mapstruct *map) { 00396 char **line; 00397 char *sql; 00398 int nrow, ncolumn, id, reg_id; 00399 const char *path = map->path; 00400 00401 if (strncmp(path, "/random/", 7) == 0) 00402 path = "/random/"; 00403 00404 reg_id = get_region_id(map->region); 00405 sql = sqlite3_mprintf("select map_id from map where map_path='%q' and map_reg_id = %d", path, reg_id); 00406 sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL); 00407 00408 if (nrow > 0) 00409 id = atoi(line[ncolumn]); 00410 else { 00411 sqlite3_free(sql); 00412 sql = sqlite3_mprintf("insert into map(map_path, map_reg_id) values( '%q', %d)", path, reg_id); 00413 do_sql(sql); 00414 id = sqlite3_last_insert_rowid(database); 00415 } 00416 sqlite3_free(sql); 00417 sqlite3_free_table(line); 00418 00419 return id; 00420 } 00421 00428 static int store_time(void) { 00429 char **line; 00430 char *sql; 00431 int nrow, ncolumn; 00432 char date[50]; 00433 time_t now; 00434 timeofday_t tod; 00435 00436 cf_get_time(&tod); 00437 now = time(NULL); 00438 00439 if (tod.day == last_stored_day) 00440 return 0; 00441 last_stored_day = tod.day; 00442 00443 snprintf(date, 50, "%10d-%2d-%2d %2d:%2d", tod.year, tod.month, tod.day, tod.hour, tod.minute); 00444 00445 sql = sqlite3_mprintf("select * from time where time_ingame='%q'", date); 00446 sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL); 00447 sqlite3_free(sql); 00448 sqlite3_free_table(line); 00449 if (nrow > 0) 00450 return 0; 00451 00452 sql = sqlite3_mprintf("insert into time (time_ingame, time_real) values( '%s', %d )", date, now); 00453 do_sql(sql); 00454 sqlite3_free(sql); 00455 return 1; 00456 } 00457 00466 static void add_player_event(object *pl, int event_code) { 00467 int id = get_living_id(pl); 00468 int map_id = 0; 00469 char *sql; 00470 00471 if (pl->map) 00472 map_id = get_map_id(pl->map); 00473 00474 sql = sqlite3_mprintf("insert into living_event values( %d, %d, %d, %d)", id, time(NULL), event_code, map_id); 00475 do_sql(sql); 00476 sqlite3_free(sql); 00477 } 00478 00489 static void add_map_event(mapstruct *map, int event_code, object *pl) { 00490 int mapid; 00491 int playerid = 0; 00492 char *sql; 00493 00494 if (pl && pl->type == PLAYER) 00495 playerid = get_living_id(pl); 00496 00497 mapid = get_map_id(map); 00498 sql = sqlite3_mprintf("insert into map_event values( %d, %d, %d, %d)", mapid, time(NULL), event_code, playerid); 00499 do_sql(sql); 00500 sqlite3_free(sql); 00501 } 00502 00513 static void add_death(object *victim, object *killer) { 00514 int vid, kid, map_id; 00515 char *sql; 00516 00517 if (!victim || !killer) 00518 return; 00519 if (victim->type != PLAYER && killer->type != PLAYER) { 00520 /* Killer might be a bullet, which might be owned by the player. */ 00521 object *owner = cf_object_get_object_property(killer, CFAPI_OBJECT_PROP_OWNER); 00522 if (owner != NULL && owner->type == PLAYER) 00523 killer = owner; 00524 else 00525 return; 00526 } 00527 00528 vid = get_living_id(victim); 00529 kid = get_living_id(killer); 00530 map_id = get_map_id(victim->map); 00531 sql = sqlite3_mprintf("insert into kill_event values( %d, %d, %d, %d, %d, %d)", time(NULL), vid, victim->level, map_id, kid, killer->level); 00532 do_sql(sql); 00533 sqlite3_free(sql); 00534 } 00535 00546 CF_PLUGIN int initPlugin(const char *iversion, f_plug_api gethooksptr) { 00547 cf_init_plugin(gethooksptr); 00548 cf_log(llevInfo, "%s init\n", PLUGIN_VERSION); 00549 return 0; 00550 } 00551 00562 CF_PLUGIN void *getPluginProperty(int *type, ...) { 00563 va_list args; 00564 const char *propname; 00565 char *buf; 00566 int size; 00567 00568 va_start(args, type); 00569 propname = va_arg(args, const char *); 00570 00571 if (!strcmp(propname, "Identification")) { 00572 buf = va_arg(args, char *); 00573 size = va_arg(args, int); 00574 va_end(args); 00575 snprintf(buf, size, PLUGIN_NAME); 00576 return NULL; 00577 } else if (!strcmp(propname, "FullName")) { 00578 buf = va_arg(args, char *); 00579 size = va_arg(args, int); 00580 va_end(args); 00581 snprintf(buf, size, PLUGIN_VERSION); 00582 return NULL; 00583 } 00584 va_end(args); 00585 return NULL; 00586 } 00587 00598 CF_PLUGIN int cflogger_runPluginCommand(object *op, char *params) { 00599 return -1; 00600 } 00601 00610 void *eventListener(int *type, ...) { 00611 static int rv = 0; 00612 00613 return &rv; 00614 } 00615 00624 CF_PLUGIN void *cflogger_globalEventListener(int *type, ...) { 00625 va_list args; 00626 static int rv = 0; 00627 player *pl; 00628 object *op; 00629 int event_code; 00630 mapstruct *map; 00631 00632 va_start(args, type); 00633 event_code = va_arg(args, int); 00634 00635 switch (event_code) { 00636 case EVENT_BORN: 00637 case EVENT_PLAYER_DEATH: 00638 case EVENT_REMOVE: 00639 case EVENT_MUZZLE: 00640 case EVENT_KICK: 00641 op = va_arg(args, object *); 00642 add_player_event(op, event_code); 00643 break; 00644 00645 case EVENT_LOGIN: 00646 case EVENT_LOGOUT: 00647 pl = va_arg(args, player *); 00648 add_player_event(pl->ob, event_code); 00649 break; 00650 00651 case EVENT_MAPENTER: 00652 case EVENT_MAPLEAVE: 00653 op = va_arg(args, object *); 00654 map = va_arg(args, mapstruct *); 00655 add_map_event(map, event_code, op); 00656 break; 00657 00658 case EVENT_MAPLOAD: 00659 case EVENT_MAPUNLOAD: 00660 case EVENT_MAPRESET: 00661 map = va_arg(args, mapstruct *); 00662 add_map_event(map, event_code, NULL); 00663 break; 00664 00665 case EVENT_GKILL: { 00666 object *killer; 00667 op = va_arg(args, object *); 00668 killer = va_arg(args, object *); 00669 add_death(op, killer); 00670 } 00671 break; 00672 00673 case EVENT_CLOCK: 00674 store_time(); 00675 break; 00676 } 00677 va_end(args); 00678 00679 return &rv; 00680 } 00681 00690 CF_PLUGIN int postInitPlugin(void) { 00691 char path[500]; 00692 const char *dir; 00693 00694 cf_log(llevInfo, "%s post init\n", PLUGIN_VERSION); 00695 00696 dir = cf_get_directory(4); 00697 snprintf(path, sizeof(path), "%s/cflogger.db", dir); 00698 cf_log(llevDebug, " [%s] database file: %s\n", PLUGIN_NAME, path); 00699 00700 if (sqlite3_open(path, &database) != SQLITE_OK) { 00701 cf_log(llevError, " [%s] database error!\n", PLUGIN_NAME); 00702 sqlite3_close(database); 00703 database = NULL; 00704 return 0; 00705 } 00706 00707 check_tables(); 00708 00709 store_time(); 00710 00711 cf_system_register_global_event(EVENT_BORN, PLUGIN_NAME, cflogger_globalEventListener); 00712 cf_system_register_global_event(EVENT_REMOVE, PLUGIN_NAME, cflogger_globalEventListener); 00713 cf_system_register_global_event(EVENT_GKILL, PLUGIN_NAME, cflogger_globalEventListener); 00714 cf_system_register_global_event(EVENT_LOGIN, PLUGIN_NAME, cflogger_globalEventListener); 00715 cf_system_register_global_event(EVENT_LOGOUT, PLUGIN_NAME, cflogger_globalEventListener); 00716 00717 cf_system_register_global_event(EVENT_PLAYER_DEATH, PLUGIN_NAME, cflogger_globalEventListener); 00718 00719 cf_system_register_global_event(EVENT_MAPENTER, PLUGIN_NAME, cflogger_globalEventListener); 00720 cf_system_register_global_event(EVENT_MAPLEAVE, PLUGIN_NAME, cflogger_globalEventListener); 00721 cf_system_register_global_event(EVENT_MAPRESET, PLUGIN_NAME, cflogger_globalEventListener); 00722 cf_system_register_global_event(EVENT_MAPLOAD, PLUGIN_NAME, cflogger_globalEventListener); 00723 cf_system_register_global_event(EVENT_MAPUNLOAD, PLUGIN_NAME, cflogger_globalEventListener); 00724 00725 cf_system_register_global_event(EVENT_MUZZLE, PLUGIN_NAME, cflogger_globalEventListener); 00726 cf_system_register_global_event(EVENT_KICK, PLUGIN_NAME, cflogger_globalEventListener); 00727 00728 cf_system_register_global_event(EVENT_CLOCK, PLUGIN_NAME, cflogger_globalEventListener); 00729 00730 return 0; 00731 } 00732 00741 CF_PLUGIN int closePlugin(void) { 00742 cf_log(llevInfo, "%s closing.\n", PLUGIN_VERSION); 00743 if (database) { 00744 sqlite3_close(database); 00745 database = NULL; 00746 } 00747 return 0; 00748 }