Increased Clientprefs error reporting and a rare leak on database connection failed.
3 * =============================================================================
4 * SourceMod Client Preferences Extension
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, version 3.0, as published by the
10 * Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * You should have received a copy of the GNU General Public License along with
18 * this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to link the
21 * code of this program (as well as its derivative works) to "Half-Life 2," the
22 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 * by the Valve Corporation. You must obey the GNU General Public License in
24 * all respects for all other code used. Additionally, AlliedModders LLC grants
25 * this exception to all derivative works. AlliedModders LLC defines further
26 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 * or <http://www.sourcemod.net/license.php>.
32 #include "extension.h"
36 * @brief Implement extension code here.
39 ClientPrefs g_ClientPrefs; /**< Global singleton for extension's main interface */
41 SMEXT_LINK(&g_ClientPrefs);
43 HandleType_t g_CookieType = 0;
44 CookieTypeHandler g_CookieTypeHandler;
46 HandleType_t g_CookieIterator = 0;
47 CookieIteratorHandler g_CookieIteratorHandler;
52 bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late)
54 queryMutex = threader->MakeMutex();
55 cookieMutex = threader->MakeMutex();
57 DBInfo = dbi->FindDatabaseConf("clientprefs");
61 DBInfo = dbi->FindDatabaseConf("default");
65 snprintf(error, maxlength, "Could not find \"clientprefs\" or \"default\" database configs");
70 if (DBInfo->driver[0] != '\0')
72 Driver = dbi->FindOrLoadDriver(DBInfo->driver);
76 Driver = dbi->GetDefaultDriver();
81 snprintf(error, maxlength, "Could not load DB Driver \"%s\"", DBInfo->driver);
86 databaseLoading = true;
87 TQueryOp *op = new TQueryOp(Query_Connect, 0);
88 dbi->AddToThreadQueue(op, PrioQueue_High);
90 dbi->AddDependency(myself, Driver);
92 sharesys->AddNatives(myself, g_ClientPrefNatives);
93 sharesys->RegisterLibrary(myself, "clientprefs");
94 g_CookieManager.cookieDataLoadedForward = forwards->CreateForward("OnClientCookiesCached", ET_Ignore, 1, NULL, Param_Cell);
96 g_CookieType = handlesys->CreateType("Cookie",
101 myself->GetIdentity(),
104 g_CookieIterator = handlesys->CreateType("CookieIterator",
105 &g_CookieIteratorHandler,
109 myself->GetIdentity(),
112 IMenuStyle *style = menus->GetDefaultStyle();
113 g_CookieManager.clientMenu = style->CreateMenu(&g_Handler, NULL);
114 g_CookieManager.clientMenu->SetDefaultTitle("Client Settings:");
116 plsys->AddPluginsListener(&g_CookieManager);
118 phrases = translator->CreatePhraseCollection();
119 phrases->AddPhraseFile("clientprefs.phrases");
120 phrases->AddPhraseFile("common.phrases");
125 void ClientPrefs::SDK_OnAllLoaded()
127 playerhelpers->AddClientListener(&g_CookieManager);
130 bool ClientPrefs::QueryInterfaceDrop(SMInterface *pInterface)
132 if ((void *)pInterface == (void *)(Database->GetDriver()))
140 void ClientPrefs::NotifyInterfaceDrop(SMInterface *pInterface)
142 if (Database != NULL && (void *)pInterface == (void *)(Database->GetDriver()))
144 InsertCookieQuery->Destroy();
145 SelectDataQuery->Destroy();
146 SelectIdQuery->Destroy();
147 InsertDataQuery->Destroy();
149 InsertCookieQuery = NULL;
150 SelectDataQuery = NULL;
151 SelectIdQuery = NULL;
152 InsertDataQuery = NULL;
159 void ClientPrefs::SDK_OnUnload()
161 handlesys->RemoveType(g_CookieType, myself->GetIdentity());
162 handlesys->RemoveType(g_CookieIterator, myself->GetIdentity());
164 g_CookieManager.Unload();
166 if (Database != NULL)
171 forwards->ReleaseForward(g_CookieManager.cookieDataLoadedForward);
173 g_CookieManager.clientMenu->Destroy();
177 plsys->RemovePluginsListener(&g_CookieManager);
178 playerhelpers->RemoveClientListener(&g_CookieManager);
180 /* Kill all our prepared queries - Queries are guaranteed to be flushed before this is called */
182 if (InsertCookieQuery != NULL)
184 InsertCookieQuery->Destroy();
187 if (SelectDataQuery != NULL)
189 SelectDataQuery->Destroy();
192 if (SelectIdQuery != NULL)
194 SelectIdQuery->Destroy();
197 if (InsertDataQuery != NULL)
199 InsertDataQuery->Destroy();
202 queryMutex->DestroyThis();
203 cookieMutex->DestroyThis();
206 void ClientPrefs::DatabaseConnect()
211 Database = Driver->Connect(DBInfo, true, error, sizeof(error));
213 if (Database == NULL)
215 g_pSM->LogError(myself, error);
216 databaseLoading = false;
221 const char *identifier = Driver->GetIdentifier();
223 if (strcmp(identifier, "sqlite") == 0)
225 driver = DRIVER_SQLITE;
227 TQueryOp *op = new TQueryOp(Query_CreateTable, 0);
229 op->SetDatabase(Database);
230 IPreparedQuery *pQuery = Database->PrepareQuery(
231 "CREATE TABLE IF NOT EXISTS sm_cookies \
233 id INTEGER PRIMARY KEY AUTOINCREMENT, \
234 name varchar(30) NOT NULL UNIQUE, \
235 description varchar(255), \
238 error, sizeof(error), &errCode);
242 g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookies: %s (%i)", error, errCode);
246 op->SetCustomPreparedQuery(pQuery);
248 dbi->AddToThreadQueue(op, PrioQueue_High);
250 op = new TQueryOp(Query_CreateTable, 0);
251 op->SetDatabase(Database);
253 pQuery = Database->PrepareQuery(
254 "CREATE TABLE IF NOT EXISTS sm_cookie_cache \
256 player varchar(65) NOT NULL, \
257 cookie_id int(10) NOT NULL, \
258 value varchar(100), \
260 PRIMARY KEY (player, cookie_id) \
262 error, sizeof(error), &errCode);
266 g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookie_cache: %s (%i)", error, errCode);
270 op->SetCustomPreparedQuery(pQuery);
272 dbi->AddToThreadQueue(op, PrioQueue_High);
274 else if (strcmp(identifier, "mysql") == 0)
276 driver = DRIVER_MYSQL;
278 TQueryOp *op = new TQueryOp(Query_CreateTable, 0);
279 op->SetDatabase(Database);
281 IPreparedQuery *pQuery = Database->PrepareQuery(
282 "CREATE TABLE IF NOT EXISTS sm_cookies \
284 id INTEGER unsigned NOT NULL auto_increment, \
285 name varchar(30) NOT NULL UNIQUE, \
286 description varchar(255), \
290 error, sizeof(error), &errCode);
294 g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookies: %s (%i)", error, errCode);
298 op->SetCustomPreparedQuery(pQuery);
300 dbi->AddToThreadQueue(op, PrioQueue_High);
302 op = new TQueryOp(Query_CreateTable, 0);
303 op->SetDatabase(Database);
305 pQuery = Database->PrepareQuery(
306 "CREATE TABLE IF NOT EXISTS sm_cookie_cache \
308 player varchar(65) NOT NULL, \
309 cookie_id int(10) NOT NULL, \
310 value varchar(100), \
311 timestamp int NOT NULL, \
312 PRIMARY KEY (player, cookie_id) \
314 error, sizeof(error), &errCode);
318 g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookie_cache: %s (%i)", error, errCode);
322 op->SetCustomPreparedQuery(pQuery);
324 dbi->AddToThreadQueue(op, PrioQueue_High);
328 g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier);
331 databaseLoading = false;
336 if (driver == DRIVER_MYSQL)
338 InsertCookieQuery = Database->PrepareQuery(
339 "INSERT IGNORE INTO sm_cookies(name, description, access) \
341 error, sizeof(error), &errCode);
343 if (InsertCookieQuery == NULL)
345 g_pSM->LogMessage(myself, "Failed to prepare query InsertCookie: %s (%i)", error, errCode);
349 InsertDataQuery = Database->PrepareQuery(
350 "INSERT INTO sm_cookie_cache(player, cookie_id, value, timestamp) \
352 ON DUPLICATE KEY UPDATE value = ?, timestamp = ?",
353 error, sizeof(error), &errCode);
355 if (InsertDataQuery == NULL)
357 g_pSM->LogMessage(myself, "Failed to prepare query InsertData: %s (%i)", error, errCode);
363 InsertCookieQuery = Database->PrepareQuery(
364 "INSERT OR IGNORE INTO sm_cookies(name, description, access) \
366 error, sizeof(error), &errCode);
368 if (InsertCookieQuery == NULL)
370 g_pSM->LogMessage(myself, "Failed to prepare query InsertCookie: %s (%i)", error, errCode);
374 InsertDataQuery = Database->PrepareQuery(
375 "INSERT OR REPLACE INTO sm_cookie_cache(player, cookie_id, value, timestamp) \
377 error, sizeof(error), &errCode);
379 if (InsertDataQuery == NULL)
381 g_pSM->LogMessage(myself, "Failed to prepare query InsertData: %s (%i)", error, errCode);
386 SelectDataQuery = Database->PrepareQuery(
387 "SELECT sm_cookies.name, sm_cookie_cache.value, sm_cookies.description, sm_cookies.access \
389 JOIN sm_cookie_cache \
390 ON sm_cookies.id = sm_cookie_cache.cookie_id \
392 error, sizeof(error), &errCode);
394 if (SelectDataQuery == NULL)
396 g_pSM->LogMessage(myself, "Failed to prepare query SelectData: %s (%i)", error, errCode);
400 SelectIdQuery = Database->PrepareQuery(
404 error, sizeof(error), &errCode);
406 if (SelectIdQuery == NULL)
408 g_pSM->LogMessage(myself, "Failed to prepare query SelectId: %s (%i)", error, errCode);
412 databaseLoading = false;
420 bool ClientPrefs::AddQueryToQueue( TQueryOp *query )
424 if (Database == NULL && databaseLoading)
426 cachedQueries.push_back(query);
427 queryMutex->Unlock();
431 queryMutex->Unlock();
435 query->SetDatabase(Database);
436 query->SetPreparedQuery();
437 dbi->AddToThreadQueue(query, PrioQueue_Normal);
443 /* If Database is NULL and we're not in the loading phase it must have failed - Can't do much */
447 void ClientPrefs::ProcessQueryCache()
449 SourceHook::List<TQueryOp *>::iterator iter;
453 iter = cachedQueries.begin();
455 while (iter != cachedQueries.end())
457 TQueryOp *op = (TQueryOp *)*iter;
459 if (Database != NULL)
461 op->SetDatabase(Database);
462 op->SetPreparedQuery();
463 dbi->AddToThreadQueue(op, PrioQueue_Normal);
473 cachedQueries.clear();
475 queryMutex->Unlock();
478 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
482 size_t len = vsnprintf(buffer, maxlength, fmt, ap);
485 if (len >= maxlength)
487 buffer[maxlength - 1] = '\0';
488 return (maxlength - 1);
496 bool Translate(char *buffer,
499 unsigned int numparams,
505 const char *fail_phrase;
506 void *params[MAX_TRANSLATE_PARAMS];
508 if (numparams > MAX_TRANSLATE_PARAMS)
514 va_start(ap, pOutLength);
515 for (i = 0; i < numparams; i++)
517 params[i] = va_arg(ap, void *);
521 if (!g_ClientPrefs.phrases->FormatString(buffer,
529 if (fail_phrase != NULL)
531 g_pSM->LogError(myself, "[SM] Could not find core phrase: %s", fail_phrase);
535 g_pSM->LogError(myself, "[SM] Unknown fatal error while translating a core phrase.");