/** * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * * Copyright (C) 2012-2018 Hercules Dev Team * Copyright (C) Athena Dev Teams * * Hercules is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define HERCULES_CORE #include "mapreg.h" #include "map/map.h" // map-"mysql_handle #include "map/script.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/db.h" #include "common/ers.h" #include "common/memmgr.h" #include "common/nullpo.h" #include "common/showmsg.h" #include "common/sql.h" #include "common/strlib.h" #include "common/timer.h" #include #include static struct mapreg_interface mapreg_s; struct mapreg_interface *mapreg; #define MAPREG_AUTOSAVE_INTERVAL (300*1000) /** * Looks up the value of an integer variable using its uid. * * @param uid variable's unique identifier. * @return variable's integer value */ static int mapreg_readreg(int64 uid) { struct mapreg_save *m = i64db_get(mapreg->regs.vars, uid); return m?m->u.i:0; } /** * Looks up the value of a string variable using its uid. * * @param uid variable's unique identifier * @return variable's string value */ static char *mapreg_readregstr(int64 uid) { struct mapreg_save *m = i64db_get(mapreg->regs.vars, uid); return m?m->u.str:NULL; } /** * Modifies the value of an integer variable. * * @param uid variable's unique identifier * @param val new value * @retval true value was successfully set */ static bool mapreg_setreg(int64 uid, int val) { struct mapreg_save *m; int num = script_getvarid(uid); unsigned int i = script_getvaridx(uid); const char* name = script->get_str(num); nullpo_retr(true, name); if( val != 0 ) { if( (m = i64db_get(mapreg->regs.vars, uid)) ) { m->u.i = val; if(name[1] != '@') { m->save = true; mapreg->dirty = true; } } else { if( i ) script->array_update(&mapreg->regs, uid, false); m = ers_alloc(mapreg->ers, struct mapreg_save); m->u.i = val; m->uid = uid; m->save = false; m->is_string = false; if (name[1] != '@' && !mapreg->skip_insert) {// write new variable to database char tmp_str[(SCRIPT_VARNAME_LENGTH+1)*2+1]; SQL->EscapeStringLen(map->mysql_handle, tmp_str, name, strnlen(name, SCRIPT_VARNAME_LENGTH+1)); if( SQL_ERROR == SQL->Query(map->mysql_handle, "INSERT INTO `%s`(`varname`,`index`,`value`) VALUES ('%s','%u','%d')", mapreg->table, tmp_str, i, val) ) Sql_ShowDebug(map->mysql_handle); } i64db_put(mapreg->regs.vars, uid, m); } } else { // val == 0 if( i ) script->array_update(&mapreg->regs, uid, true); if( (m = i64db_get(mapreg->regs.vars, uid)) ) { ers_free(mapreg->ers, m); } i64db_remove(mapreg->regs.vars, uid); if( name[1] != '@' ) {// Remove from database because it is unused. if( SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `varname`='%s' AND `index`='%u'", mapreg->table, name, i) ) Sql_ShowDebug(map->mysql_handle); } } return true; } /** * Modifies the value of a string variable. * * @param uid variable's unique identifier * @param str new value * @retval true value was successfully set */ static bool mapreg_setregstr(int64 uid, const char *str) { struct mapreg_save *m; int num = script_getvarid(uid); unsigned int i = script_getvaridx(uid); const char* name = script->get_str(num); nullpo_retr(true, name); if( str == NULL || *str == 0 ) { if( i ) script->array_update(&mapreg->regs, uid, true); if(name[1] != '@') { if (SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `varname`='%s' AND `index`='%u'", mapreg->table, name, i)) Sql_ShowDebug(map->mysql_handle); } if( (m = i64db_get(mapreg->regs.vars, uid)) ) { if( m->u.str != NULL ) aFree(m->u.str); ers_free(mapreg->ers, m); } i64db_remove(mapreg->regs.vars, uid); } else { if( (m = i64db_get(mapreg->regs.vars, uid)) ) { if( m->u.str != NULL ) aFree(m->u.str); m->u.str = aStrdup(str); if(name[1] != '@') { mapreg->dirty = true; m->save = true; } } else { if( i ) script->array_update(&mapreg->regs, uid, false); m = ers_alloc(mapreg->ers, struct mapreg_save); m->uid = uid; m->u.str = aStrdup(str); m->save = false; m->is_string = true; if(name[1] != '@' && !mapreg->skip_insert) { //put returned null, so we must insert. char tmp_str[(SCRIPT_VARNAME_LENGTH+1)*2+1]; char tmp_str2[255*2+1]; SQL->EscapeStringLen(map->mysql_handle, tmp_str, name, strnlen(name, SCRIPT_VARNAME_LENGTH+1)); SQL->EscapeStringLen(map->mysql_handle, tmp_str2, str, strnlen(str, 255)); if( SQL_ERROR == SQL->Query(map->mysql_handle, "INSERT INTO `%s`(`varname`,`index`,`value`) VALUES ('%s','%u','%s')", mapreg->table, tmp_str, i, tmp_str2) ) Sql_ShowDebug(map->mysql_handle); } i64db_put(mapreg->regs.vars, uid, m); } } return true; } /** * Loads permanent variables from database. */ static void script_load_mapreg(void) { /* 0 1 2 +-------------------------+ | varname | index | value | +-------------------------+ */ struct SqlStmt *stmt = SQL->StmtMalloc(map->mysql_handle); char varname[SCRIPT_VARNAME_LENGTH+1]; int index; char value[255+1]; uint32 length; if ( SQL_ERROR == SQL->StmtPrepare(stmt, "SELECT `varname`, `index`, `value` FROM `%s`", mapreg->table) || SQL_ERROR == SQL->StmtExecute(stmt) ) { SqlStmt_ShowDebug(stmt); SQL->StmtFree(stmt); return; } mapreg->skip_insert = true; SQL->StmtBindColumn(stmt, 0, SQLDT_STRING, &varname, sizeof varname, &length, NULL); SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &index, sizeof index, NULL, NULL); SQL->StmtBindColumn(stmt, 2, SQLDT_STRING, &value, sizeof value, NULL, NULL); while ( SQL_SUCCESS == SQL->StmtNextRow(stmt) ) { int s = script->add_variable(varname); int i = index; if( i64db_exists(mapreg->regs.vars, reference_uid(s, i)) ) { ShowWarning("load_mapreg: duplicate! '%s' => '%s' skipping...\n",varname,value); continue; } if( varname[length-1] == '$' ) { mapreg->setregstr(reference_uid(s, i),value); } else { mapreg->setreg(reference_uid(s, i),atoi(value)); } } SQL->StmtFree(stmt); mapreg->skip_insert = false; mapreg->dirty = false; } /** * Saves permanent variables to database. */ static void script_save_mapreg(void) { if (mapreg->dirty) { struct DBIterator *iter = db_iterator(mapreg->regs.vars); struct mapreg_save *m = NULL; for (m = dbi_first(iter); dbi_exists(iter); m = dbi_next(iter)) { if (m->save) { int num = script_getvarid(m->uid); int i = script_getvaridx(m->uid); const char* name = script->get_str(num); nullpo_retv(name); if (!m->is_string) { if( SQL_ERROR == SQL->Query(map->mysql_handle, "UPDATE `%s` SET `value`='%d' WHERE `varname`='%s' AND `index`='%d' LIMIT 1", mapreg->table, m->u.i, name, i) ) Sql_ShowDebug(map->mysql_handle); } else { char tmp_str2[2*255+1]; SQL->EscapeStringLen(map->mysql_handle, tmp_str2, m->u.str, safestrnlen(m->u.str, 255)); if( SQL_ERROR == SQL->Query(map->mysql_handle, "UPDATE `%s` SET `value`='%s' WHERE `varname`='%s' AND `index`='%d' LIMIT 1", mapreg->table, tmp_str2, name, i) ) Sql_ShowDebug(map->mysql_handle); } m->save = false; } } dbi_destroy(iter); mapreg->dirty = false; } } /** * Timer event to auto-save permanent variables. * * @see timer->do_timer */ static int script_autosave_mapreg(int tid, int64 tick, int id, intptr_t data) { mapreg->save(); return 0; } /** * Destroys a mapreg_save structure, freeing the contained string, if any. * * @see DBApply */ static int mapreg_destroyreg(union DBKey key, struct DBData *data, va_list ap) { struct mapreg_save *m = NULL; if (data->type != DB_DATA_PTR) // Sanity check return 0; m = DB->data2ptr(data); if (m->is_string) { if (m->u.str) aFree(m->u.str); } ers_free(mapreg->ers, m); return 0; } /** * Reloads mapregs, saving to database beforehand. * * This has the effect of clearing the temporary variables, and * reloading the permanent ones. */ static void mapreg_reload(void) { mapreg->save(); mapreg->regs.vars->clear(mapreg->regs.vars, mapreg->destroyreg); if( mapreg->regs.arrays ) { mapreg->regs.arrays->destroy(mapreg->regs.arrays, script->array_free_db); mapreg->regs.arrays = NULL; } mapreg->load(); } /** * Finalizer. */ static void mapreg_final(void) { mapreg->save(); mapreg->regs.vars->destroy(mapreg->regs.vars, mapreg->destroyreg); ers_destroy(mapreg->ers); if( mapreg->regs.arrays ) mapreg->regs.arrays->destroy(mapreg->regs.arrays, script->array_free_db); } /** * Initializer. */ static void mapreg_init(void) { mapreg->regs.vars = i64db_alloc(DB_OPT_BASE); mapreg->ers = ers_new(sizeof(struct mapreg_save), "mapreg_sql.c::mapreg_ers", ERS_OPT_CLEAN); mapreg->load(); timer->add_func_list(mapreg->save_timer, "mapreg_script_autosave_mapreg"); timer->add_interval(timer->gettick() + MAPREG_AUTOSAVE_INTERVAL, mapreg->save_timer, 0, 0, MAPREG_AUTOSAVE_INTERVAL); } /** * Loads the mapreg configuration file. * * @param filename Path to configuration file (used in error and warning messages). * @param config The current config being parsed. * @param imported Whether the current config is imported from another file. * * @retval false in case of error. */ static bool mapreg_config_read(const char *filename, const struct config_setting_t *config, bool imported) { nullpo_retr(false, filename); nullpo_retr(false, config); if (libconfig->setting_lookup_mutable_string(config, "mapreg_db", mapreg->table, sizeof(mapreg->table)) != CONFIG_TRUE) return false; return true; } /** * Interface defaults initializer. */ void mapreg_defaults(void) { mapreg = &mapreg_s; /* */ mapreg->regs.vars = NULL; mapreg->ers = NULL; mapreg->skip_insert = false; safestrncpy(mapreg->table, "mapreg", sizeof(mapreg->table)); mapreg->dirty = false; /* */ mapreg->regs.arrays = NULL; /* */ mapreg->init = mapreg_init; mapreg->final = mapreg_final; /* */ mapreg->readreg = mapreg_readreg; mapreg->readregstr = mapreg_readregstr; mapreg->setreg = mapreg_setreg; mapreg->setregstr = mapreg_setregstr; mapreg->load = script_load_mapreg; mapreg->save = script_save_mapreg; mapreg->save_timer = script_autosave_mapreg; mapreg->destroyreg = mapreg_destroyreg; mapreg->reload = mapreg_reload; mapreg->config_read = mapreg_config_read; }