pred@2009: /** pred@2009: * vim: set ts=4 : pred@2009: * ============================================================================= pred@2009: * SourceMod Client Preferences Extension pred@2009: * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. pred@2009: * ============================================================================= pred@2009: * pred@2009: * This program is free software; you can redistribute it and/or modify it under pred@2009: * the terms of the GNU General Public License, version 3.0, as published by the pred@2009: * Free Software Foundation. pred@2009: * pred@2009: * This program is distributed in the hope that it will be useful, but WITHOUT pred@2009: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS pred@2009: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more pred@2009: * details. pred@2009: * pred@2009: * You should have received a copy of the GNU General Public License along with pred@2009: * this program. If not, see . pred@2009: * pred@2009: * As a special exception, AlliedModders LLC gives you permission to link the pred@2009: * code of this program (as well as its derivative works) to "Half-Life 2," the pred@2009: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software pred@2009: * by the Valve Corporation. You must obey the GNU General Public License in pred@2009: * all respects for all other code used. Additionally, AlliedModders LLC grants pred@2009: * this exception to all derivative works. AlliedModders LLC defines further pred@2009: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), pred@2009: * or . pred@2009: * pred@2009: * Version: $Id$ pred@2009: */ pred@2009: pred@2009: #include "extension.h" pred@2009: pred@2009: /** pred@2009: * @file extension.cpp pred@2009: * @brief Implement extension code here. pred@2009: */ pred@2009: pred@2009: ClientPrefs g_ClientPrefs; /**< Global singleton for extension's main interface */ pred@2009: pred@2009: SMEXT_LINK(&g_ClientPrefs); pred@2009: pred@2009: HandleType_t g_CookieType = 0; pred@2009: CookieTypeHandler g_CookieTypeHandler; pred@2009: pred@2048: HandleType_t g_CookieIterator = 0; pred@2048: CookieIteratorHandler g_CookieIteratorHandler; pred@2048: pred@2009: pred@2009: int driver = 0; pred@2009: pred@2009: bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late) pred@2207: { pred@2207: queryMutex = threader->MakeMutex(); pred@2207: cookieMutex = threader->MakeMutex(); pred@2207: pred@2207: DBInfo = dbi->FindDatabaseConf("clientprefs"); pred@2009: pred@2009: if (DBInfo == NULL) pred@2009: { pred@2009: DBInfo = dbi->FindDatabaseConf("default"); pred@2009: pred@2009: if (DBInfo == NULL) pred@2009: { pred@2009: snprintf(error, maxlength, "Could not find \"clientprefs\" or \"default\" database configs"); pred@2009: return false; pred@2009: } pred@2009: } pred@2009: pred@2009: if (DBInfo->driver[0] != '\0') pred@2009: { pred@2009: Driver = dbi->FindOrLoadDriver(DBInfo->driver); pred@2009: } pred@2009: else pred@2009: { pred@2009: Driver = dbi->GetDefaultDriver(); pred@2009: } pred@2009: pred@2009: if (Driver == NULL) pred@2009: { pred@2009: snprintf(error, maxlength, "Could not load DB Driver \"%s\"", DBInfo->driver); pred@2009: return false; pred@2009: } pred@2009: pred@2207: Database = NULL; pred@2207: databaseLoading = true; pred@2207: TQueryOp *op = new TQueryOp(Query_Connect, 0); pred@2207: dbi->AddToThreadQueue(op, PrioQueue_High); pred@2009: dvander@2135: dbi->AddDependency(myself, Driver); dvander@2135: pred@2009: sharesys->AddNatives(myself, g_ClientPrefNatives); pred@2009: sharesys->RegisterLibrary(myself, "clientprefs"); pred@2048: g_CookieManager.cookieDataLoadedForward = forwards->CreateForward("OnClientCookiesCached", ET_Ignore, 1, NULL, Param_Cell); pred@2009: pred@2009: g_CookieType = handlesys->CreateType("Cookie", pred@2009: &g_CookieTypeHandler, pred@2009: 0, pred@2009: NULL, pred@2009: NULL, pred@2009: myself->GetIdentity(), pred@2009: NULL); pred@2009: pred@2048: g_CookieIterator = handlesys->CreateType("CookieIterator", pred@2048: &g_CookieIteratorHandler, pred@2048: 0, pred@2048: NULL, pred@2048: NULL, pred@2048: myself->GetIdentity(), pred@2048: NULL); pred@2048: pred@2048: IMenuStyle *style = menus->GetDefaultStyle(); pred@2048: g_CookieManager.clientMenu = style->CreateMenu(&g_Handler, NULL); pred@2048: g_CookieManager.clientMenu->SetDefaultTitle("Client Settings:"); pred@2048: pred@2048: plsys->AddPluginsListener(&g_CookieManager); pred@2048: pred@2048: phrases = translator->CreatePhraseCollection(); pred@2048: phrases->AddPhraseFile("clientprefs.phrases"); pred@2048: phrases->AddPhraseFile("common.phrases"); pred@2048: pred@2009: return true; pred@2009: } pred@2009: pred@2009: void ClientPrefs::SDK_OnAllLoaded() pred@2009: { pred@2009: playerhelpers->AddClientListener(&g_CookieManager); pred@2009: } pred@2009: dvander@2135: bool ClientPrefs::QueryInterfaceDrop(SMInterface *pInterface) dvander@2135: { dvander@2135: if ((void *)pInterface == (void *)(Database->GetDriver())) dvander@2135: { dvander@2135: return false; dvander@2135: } dvander@2135: dvander@2135: return true; dvander@2135: } dvander@2135: dvander@2135: void ClientPrefs::NotifyInterfaceDrop(SMInterface *pInterface) dvander@2135: { dvander@2135: if (Database != NULL && (void *)pInterface == (void *)(Database->GetDriver())) dvander@2135: { pred@2207: InsertCookieQuery->Destroy(); pred@2207: SelectDataQuery->Destroy(); pred@2207: SelectIdQuery->Destroy(); pred@2207: InsertDataQuery->Destroy(); pred@2207: pred@2207: InsertCookieQuery = NULL; pred@2207: SelectDataQuery = NULL; pred@2207: SelectIdQuery = NULL; pred@2207: InsertDataQuery = NULL; pred@2207: dvander@2135: Database->Close(); dvander@2135: Database = NULL; dvander@2135: } dvander@2135: } dvander@2135: pred@2009: void ClientPrefs::SDK_OnUnload() pred@2009: { pred@2009: handlesys->RemoveType(g_CookieType, myself->GetIdentity()); pred@2131: handlesys->RemoveType(g_CookieIterator, myself->GetIdentity()); pred@2009: pred@2009: g_CookieManager.Unload(); pred@2009: dvander@2135: if (Database != NULL) dvander@2135: { dvander@2135: Database->Close(); dvander@2135: } pred@2009: pred@2048: forwards->ReleaseForward(g_CookieManager.cookieDataLoadedForward); pred@2048: pred@2048: g_CookieManager.clientMenu->Destroy(); pred@2131: pred@2131: phrases->Destroy(); pred@2131: pred@2131: plsys->RemovePluginsListener(&g_CookieManager); pred@2131: playerhelpers->RemoveClientListener(&g_CookieManager); pred@2207: pred@2207: /* Kill all our prepared queries - Queries are guaranteed to be flushed before this is called */ pred@2207: pred@2207: if (InsertCookieQuery != NULL) pred@2207: { pred@2207: InsertCookieQuery->Destroy(); pred@2207: } pred@2207: pred@2207: if (SelectDataQuery != NULL) pred@2207: { pred@2207: SelectDataQuery->Destroy(); pred@2207: } pred@2207: pred@2207: if (SelectIdQuery != NULL) pred@2207: { pred@2207: SelectIdQuery->Destroy(); pred@2207: } pred@2207: pred@2207: if (InsertDataQuery != NULL) pred@2207: { pred@2207: InsertDataQuery->Destroy(); pred@2207: } pred@2207: pred@2207: queryMutex->DestroyThis(); pred@2207: cookieMutex->DestroyThis(); pred@2207: } pred@2207: pred@2207: void ClientPrefs::DatabaseConnect() pred@2207: { pred@2207: char error[256]; pred@2207: int errCode = 0; pred@2207: pred@2207: Database = Driver->Connect(DBInfo, true, error, sizeof(error)); pred@2207: pred@2207: if (Database == NULL) pred@2207: { pred@2207: g_pSM->LogError(myself, error); pred@2207: databaseLoading = false; pred@2207: ProcessQueryCache(); pred@2207: return; pred@2207: } pred@2207: pred@2207: const char *identifier = Driver->GetIdentifier(); pred@2207: pred@2207: if (strcmp(identifier, "sqlite") == 0) pred@2207: { pred@2207: driver = DRIVER_SQLITE; pred@2207: pred@2207: TQueryOp *op = new TQueryOp(Query_CreateTable, 0); pred@2207: pred@2207: op->SetDatabase(Database); pred@2220: IPreparedQuery *pQuery = Database->PrepareQuery( pred@2207: "CREATE TABLE IF NOT EXISTS sm_cookies \ pred@2207: ( \ pred@2207: id INTEGER PRIMARY KEY AUTOINCREMENT, \ pred@2207: name varchar(30) NOT NULL UNIQUE, \ pred@2207: description varchar(255), \ pred@2207: access INTEGER \ pred@2207: )", pred@2220: error, sizeof(error), &errCode); pred@2220: pred@2220: if (pQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookies: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2220: pred@2220: op->SetCustomPreparedQuery(pQuery); pred@2207: pred@2207: dbi->AddToThreadQueue(op, PrioQueue_High); pred@2207: pred@2207: op = new TQueryOp(Query_CreateTable, 0); pred@2207: op->SetDatabase(Database); pred@2220: pred@2220: pQuery = Database->PrepareQuery( pred@2207: "CREATE TABLE IF NOT EXISTS sm_cookie_cache \ pred@2207: ( \ pred@2207: player varchar(65) NOT NULL, \ pred@2207: cookie_id int(10) NOT NULL, \ pred@2207: value varchar(100), \ pred@2207: timestamp int, \ pred@2207: PRIMARY KEY (player, cookie_id) \ pred@2207: )", pred@2220: error, sizeof(error), &errCode); pred@2220: pred@2220: if (pQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookie_cache: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2220: pred@2220: op->SetCustomPreparedQuery(pQuery); pred@2207: pred@2207: dbi->AddToThreadQueue(op, PrioQueue_High); pred@2207: } pred@2207: else if (strcmp(identifier, "mysql") == 0) pred@2207: { pred@2207: driver = DRIVER_MYSQL; pred@2207: pred@2207: TQueryOp *op = new TQueryOp(Query_CreateTable, 0); pred@2207: op->SetDatabase(Database); pred@2220: pred@2220: IPreparedQuery *pQuery = Database->PrepareQuery( pred@2207: "CREATE TABLE IF NOT EXISTS sm_cookies \ pred@2207: ( \ pred@2207: id INTEGER unsigned NOT NULL auto_increment, \ pred@2207: name varchar(30) NOT NULL UNIQUE, \ pred@2207: description varchar(255), \ pred@2207: access INTEGER, \ pred@2207: PRIMARY KEY (id) \ pred@2207: )", pred@2220: error, sizeof(error), &errCode); pred@2220: pred@2220: if (pQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookies: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2220: pred@2220: op->SetCustomPreparedQuery(pQuery); pred@2207: pred@2207: dbi->AddToThreadQueue(op, PrioQueue_High); pred@2207: pred@2207: op = new TQueryOp(Query_CreateTable, 0); pred@2207: op->SetDatabase(Database); pred@2220: pred@2220: pQuery = Database->PrepareQuery( pred@2207: "CREATE TABLE IF NOT EXISTS sm_cookie_cache \ pred@2207: ( \ pred@2207: player varchar(65) NOT NULL, \ pred@2207: cookie_id int(10) NOT NULL, \ pred@2207: value varchar(100), \ pred@2207: timestamp int NOT NULL, \ pred@2207: PRIMARY KEY (player, cookie_id) \ pred@2207: )", pred@2220: error, sizeof(error), &errCode); pred@2220: pred@2220: if (pQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookie_cache: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2220: pred@2220: op->SetCustomPreparedQuery(pQuery); pred@2207: pred@2207: dbi->AddToThreadQueue(op, PrioQueue_High); pred@2207: } pred@2207: else pred@2207: { pred@2207: g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier); pred@2207: Database->Close(); pred@2207: Database = NULL; pred@2207: databaseLoading = false; pred@2207: ProcessQueryCache(); pred@2207: return; pred@2207: } pred@2207: pred@2207: if (driver == DRIVER_MYSQL) pred@2207: { pred@2207: InsertCookieQuery = Database->PrepareQuery( pred@2207: "INSERT IGNORE INTO sm_cookies(name, description, access) \ pred@2207: VALUES(?, ?, ?)", pred@2207: error, sizeof(error), &errCode); pred@2220: pred@2220: if (InsertCookieQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query InsertCookie: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2220: pred@2207: InsertDataQuery = Database->PrepareQuery( pred@2207: "INSERT INTO sm_cookie_cache(player, cookie_id, value, timestamp) \ pred@2207: VALUES(?, ?, ?, ?) \ pred@2207: ON DUPLICATE KEY UPDATE value = ?, timestamp = ?", pred@2207: error, sizeof(error), &errCode); pred@2220: pred@2220: if (InsertDataQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query InsertData: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2207: } pred@2207: else pred@2207: { pred@2207: InsertCookieQuery = Database->PrepareQuery( pred@2207: "INSERT OR IGNORE INTO sm_cookies(name, description, access) \ pred@2207: VALUES(?, ?, ?)", pred@2207: error, sizeof(error), &errCode); pred@2220: pred@2220: if (InsertCookieQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query InsertCookie: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2220: pred@2207: InsertDataQuery = Database->PrepareQuery( pred@2207: "INSERT OR REPLACE INTO sm_cookie_cache(player, cookie_id, value, timestamp) \ pred@2207: VALUES(?, ?, ?, ?)", pred@2207: error, sizeof(error), &errCode); pred@2220: pred@2220: if (InsertDataQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query InsertData: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2207: } pred@2207: pred@2207: SelectDataQuery = Database->PrepareQuery( pred@2207: "SELECT sm_cookies.name, sm_cookie_cache.value, sm_cookies.description, sm_cookies.access \ pred@2207: FROM sm_cookies \ pred@2207: JOIN sm_cookie_cache \ pred@2207: ON sm_cookies.id = sm_cookie_cache.cookie_id \ pred@2207: WHERE player = ?", pred@2207: error, sizeof(error), &errCode); pred@2207: pred@2220: if (SelectDataQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query SelectData: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2220: pred@2207: SelectIdQuery = Database->PrepareQuery( pred@2207: "SELECT id \ pred@2207: FROM sm_cookies \ pred@2207: WHERE name=?", pred@2207: error, sizeof(error), &errCode); pred@2207: pred@2220: if (SelectIdQuery == NULL) pred@2220: { pred@2220: g_pSM->LogMessage(myself, "Failed to prepare query SelectId: %s (%i)", error, errCode); pred@2220: return; pred@2220: } pred@2220: pred@2207: databaseLoading = false; pred@2207: cell_t result = 0; pred@2207: pred@2207: ProcessQueryCache(); pred@2207: pred@2207: return; pred@2207: } pred@2207: pred@2207: bool ClientPrefs::AddQueryToQueue( TQueryOp *query ) pred@2207: { pred@2207: queryMutex->Lock(); pred@2207: pred@2207: if (Database == NULL && databaseLoading) pred@2207: { pred@2207: cachedQueries.push_back(query); pred@2207: queryMutex->Unlock(); pred@2207: return true; pred@2207: } pred@2207: pred@2207: queryMutex->Unlock(); pred@2207: pred@2207: if (Database) pred@2207: { pred@2207: query->SetDatabase(Database); pred@2207: query->SetPreparedQuery(); pred@2207: dbi->AddToThreadQueue(query, PrioQueue_Normal); pred@2207: return true; pred@2207: } pred@2207: pred@2220: query->Destroy(); pred@2220: pred@2207: /* If Database is NULL and we're not in the loading phase it must have failed - Can't do much */ pred@2207: return false; pred@2207: } pred@2207: pred@2207: void ClientPrefs::ProcessQueryCache() pred@2207: { pred@2207: SourceHook::List::iterator iter; pred@2207: pred@2207: queryMutex->Lock(); pred@2207: pred@2207: iter = cachedQueries.begin(); pred@2207: pred@2207: while (iter != cachedQueries.end()) pred@2207: { pred@2207: TQueryOp *op = (TQueryOp *)*iter; pred@2207: pred@2207: if (Database != NULL) pred@2207: { pred@2207: op->SetDatabase(Database); pred@2207: op->SetPreparedQuery(); pred@2207: dbi->AddToThreadQueue(op, PrioQueue_Normal); pred@2207: } pred@2207: else pred@2207: { pred@2207: delete op; pred@2207: } pred@2207: pred@2207: iter++; pred@2207: } pred@2207: pred@2207: cachedQueries.clear(); pred@2207: pred@2207: queryMutex->Unlock(); pred@2048: } pred@2048: pred@2048: size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) pred@2048: { pred@2048: va_list ap; pred@2048: va_start(ap, fmt); pred@2048: size_t len = vsnprintf(buffer, maxlength, fmt, ap); pred@2048: va_end(ap); pred@2048: pred@2048: if (len >= maxlength) pred@2048: { pred@2048: buffer[maxlength - 1] = '\0'; pred@2048: return (maxlength - 1); pred@2048: } pred@2048: else pred@2048: { pred@2048: return len; pred@2048: } pred@2048: } pred@2048: pred@2048: bool Translate(char *buffer, pred@2048: size_t maxlength, pred@2048: const char *format, pred@2048: unsigned int numparams, pred@2048: size_t *pOutLength, pred@2048: ...) pred@2048: { pred@2048: va_list ap; pred@2048: unsigned int i; pred@2048: const char *fail_phrase; pred@2048: void *params[MAX_TRANSLATE_PARAMS]; pred@2048: pred@2048: if (numparams > MAX_TRANSLATE_PARAMS) pred@2048: { pred@2048: assert(false); pred@2048: return false; pred@2048: } pred@2048: pred@2048: va_start(ap, pOutLength); pred@2048: for (i = 0; i < numparams; i++) pred@2048: { pred@2048: params[i] = va_arg(ap, void *); pred@2048: } pred@2048: va_end(ap); pred@2048: pred@2048: if (!g_ClientPrefs.phrases->FormatString(buffer, pred@2048: maxlength, pred@2048: format, pred@2048: params, pred@2048: numparams, pred@2048: pOutLength, pred@2048: &fail_phrase)) pred@2048: { pred@2048: if (fail_phrase != NULL) pred@2048: { pred@2048: g_pSM->LogError(myself, "[SM] Could not find core phrase: %s", fail_phrase); pred@2048: } pred@2048: else pred@2048: { pred@2048: g_pSM->LogError(myself, "[SM] Unknown fatal error while translating a core phrase."); pred@2048: } pred@2048: pred@2048: return false; pred@2048: } pred@2048: pred@2048: return true; pred@2009: } pred@2009: pred@2009: pred@2048: