extensions/clientprefs/extension.cpp
author Matt Woodrow <pred@alliedmods.net>
Tue Aug 05 08:00:54 2008 +0000 (2008-08-05)
changeset 2220 20b404790b3b
parent 2207 3a97a56ded62
child 2338 b9a152a270c9
permissions -rw-r--r--
Increased Clientprefs error reporting and a rare leak on database connection failed.
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod Client Preferences Extension
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     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.
    11  * 
    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
    15  * details.
    16  *
    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/>.
    19  *
    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>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include "extension.h"
    33 
    34 /**
    35  * @file extension.cpp
    36  * @brief Implement extension code here.
    37  */
    38 
    39 ClientPrefs g_ClientPrefs;		/**< Global singleton for extension's main interface */
    40 
    41 SMEXT_LINK(&g_ClientPrefs);
    42 
    43 HandleType_t g_CookieType = 0;
    44 CookieTypeHandler g_CookieTypeHandler;
    45 
    46 HandleType_t g_CookieIterator = 0;
    47 CookieIteratorHandler g_CookieIteratorHandler;
    48 
    49 
    50 int driver = 0;
    51 
    52 bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late)
    53 {
    54 	queryMutex = threader->MakeMutex();
    55 	cookieMutex = threader->MakeMutex();
    56 
    57 	DBInfo = dbi->FindDatabaseConf("clientprefs");
    58 
    59 	if (DBInfo == NULL)
    60 	{
    61 		DBInfo = dbi->FindDatabaseConf("default");
    62 
    63 		if (DBInfo == NULL)
    64 		{
    65 			snprintf(error, maxlength, "Could not find \"clientprefs\" or \"default\" database configs");
    66 			return false;
    67 		}
    68 	}
    69 
    70 	if (DBInfo->driver[0] != '\0')
    71 	{
    72 		Driver = dbi->FindOrLoadDriver(DBInfo->driver);
    73 	}
    74 	else
    75 	{
    76 		Driver = dbi->GetDefaultDriver();
    77 	}
    78 
    79 	if (Driver == NULL)
    80 	{
    81 		snprintf(error, maxlength, "Could not load DB Driver \"%s\"", DBInfo->driver);
    82 		return false;
    83 	}
    84 
    85 	Database = NULL;
    86 	databaseLoading = true;
    87 	TQueryOp *op = new TQueryOp(Query_Connect, 0);
    88 	dbi->AddToThreadQueue(op, PrioQueue_High);
    89 
    90 	dbi->AddDependency(myself, Driver);
    91 
    92 	sharesys->AddNatives(myself, g_ClientPrefNatives);
    93 	sharesys->RegisterLibrary(myself, "clientprefs");
    94 	g_CookieManager.cookieDataLoadedForward = forwards->CreateForward("OnClientCookiesCached", ET_Ignore, 1, NULL, Param_Cell);
    95 
    96 	g_CookieType = handlesys->CreateType("Cookie", 
    97 		&g_CookieTypeHandler, 
    98 		0, 
    99 		NULL, 
   100 		NULL, 
   101 		myself->GetIdentity(), 
   102 		NULL);
   103 
   104 	g_CookieIterator = handlesys->CreateType("CookieIterator", 
   105 		&g_CookieIteratorHandler, 
   106 		0, 
   107 		NULL, 
   108 		NULL, 
   109 		myself->GetIdentity(), 
   110 		NULL);
   111 
   112 	IMenuStyle *style = menus->GetDefaultStyle();
   113 	g_CookieManager.clientMenu = style->CreateMenu(&g_Handler, NULL);
   114 	g_CookieManager.clientMenu->SetDefaultTitle("Client Settings:");
   115 
   116 	plsys->AddPluginsListener(&g_CookieManager);
   117 
   118 	phrases = translator->CreatePhraseCollection();
   119 	phrases->AddPhraseFile("clientprefs.phrases");
   120 	phrases->AddPhraseFile("common.phrases");
   121 
   122 	return true;
   123 }
   124 
   125 void ClientPrefs::SDK_OnAllLoaded()
   126 {
   127 	playerhelpers->AddClientListener(&g_CookieManager);
   128 }
   129 
   130 bool ClientPrefs::QueryInterfaceDrop(SMInterface *pInterface)
   131 {
   132 	if ((void *)pInterface == (void *)(Database->GetDriver()))
   133 	{
   134 		return false;
   135 	}
   136 
   137 	return true;
   138 }
   139 
   140 void ClientPrefs::NotifyInterfaceDrop(SMInterface *pInterface)
   141 {
   142 	if (Database != NULL && (void *)pInterface == (void *)(Database->GetDriver()))
   143 	{
   144 		InsertCookieQuery->Destroy();
   145 		SelectDataQuery->Destroy();
   146 		SelectIdQuery->Destroy();
   147 		InsertDataQuery->Destroy();
   148 
   149 		InsertCookieQuery = NULL;
   150 		SelectDataQuery = NULL;
   151 		SelectIdQuery = NULL;
   152 		InsertDataQuery = NULL;
   153 
   154 		Database->Close();
   155 		Database = NULL;
   156 	}
   157 }
   158 
   159 void ClientPrefs::SDK_OnUnload()
   160 {
   161 	handlesys->RemoveType(g_CookieType, myself->GetIdentity());
   162 	handlesys->RemoveType(g_CookieIterator, myself->GetIdentity());
   163 
   164 	g_CookieManager.Unload();
   165 
   166 	if (Database != NULL)
   167 	{
   168 		Database->Close();
   169 	}
   170 
   171 	forwards->ReleaseForward(g_CookieManager.cookieDataLoadedForward);
   172 
   173 	g_CookieManager.clientMenu->Destroy();
   174 
   175 	phrases->Destroy();
   176 
   177 	plsys->RemovePluginsListener(&g_CookieManager);
   178 	playerhelpers->RemoveClientListener(&g_CookieManager);
   179 
   180 	/* Kill all our prepared queries - Queries are guaranteed to be flushed before this is called */
   181 
   182 	if (InsertCookieQuery != NULL)
   183 	{
   184 		InsertCookieQuery->Destroy();
   185 	}
   186 
   187 	if (SelectDataQuery != NULL)
   188 	{
   189 		SelectDataQuery->Destroy();
   190 	}
   191 
   192 	if (SelectIdQuery != NULL)
   193 	{
   194 		SelectIdQuery->Destroy();
   195 	}
   196 
   197 	if (InsertDataQuery != NULL)
   198 	{
   199 		InsertDataQuery->Destroy();
   200 	}
   201 
   202 	queryMutex->DestroyThis();
   203 	cookieMutex->DestroyThis();
   204 }
   205 
   206 void ClientPrefs::DatabaseConnect()
   207 {
   208 	char error[256];
   209 	int errCode = 0;
   210 
   211 	Database = Driver->Connect(DBInfo, true, error, sizeof(error));
   212 
   213 	if (Database == NULL)
   214 	{
   215 		g_pSM->LogError(myself, error);
   216 		databaseLoading = false;
   217 		ProcessQueryCache();
   218 		return;
   219 	}
   220 
   221 	const char *identifier = Driver->GetIdentifier();
   222 
   223 	if (strcmp(identifier, "sqlite") == 0)
   224 	{
   225 		driver = DRIVER_SQLITE;
   226 
   227 		TQueryOp *op = new TQueryOp(Query_CreateTable, 0);
   228 
   229 		op->SetDatabase(Database);
   230 		IPreparedQuery *pQuery = Database->PrepareQuery(
   231 				"CREATE TABLE IF NOT EXISTS sm_cookies  \
   232 				( \
   233 					id INTEGER PRIMARY KEY AUTOINCREMENT, \
   234 					name varchar(30) NOT NULL UNIQUE, \
   235 					description varchar(255), \
   236 					access INTEGER \
   237 				)",
   238 				error, sizeof(error), &errCode);
   239 
   240 		if (pQuery == NULL)
   241 		{
   242 			g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookies: %s (%i)", error, errCode);
   243 			return;
   244 		}
   245 
   246 		op->SetCustomPreparedQuery(pQuery);
   247 
   248 		dbi->AddToThreadQueue(op, PrioQueue_High);
   249 
   250 		op = new TQueryOp(Query_CreateTable, 0);
   251 		op->SetDatabase(Database);
   252 
   253 		pQuery = Database->PrepareQuery(
   254 				"CREATE TABLE IF NOT EXISTS sm_cookie_cache \
   255 				( \
   256 					player varchar(65) NOT NULL, \
   257 					cookie_id int(10) NOT NULL, \
   258 					value varchar(100), \
   259 					timestamp int, \
   260 					PRIMARY KEY (player, cookie_id) \
   261 				)",
   262 				error, sizeof(error), &errCode);
   263 
   264 		if (pQuery == NULL)
   265 		{
   266 			g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookie_cache: %s (%i)", error, errCode);
   267 			return;
   268 		}
   269 
   270 		op->SetCustomPreparedQuery(pQuery);
   271 
   272 		dbi->AddToThreadQueue(op, PrioQueue_High);
   273 	}
   274 	else if (strcmp(identifier, "mysql") == 0)
   275 	{
   276 		driver = DRIVER_MYSQL;
   277 
   278 		TQueryOp *op = new TQueryOp(Query_CreateTable, 0);
   279 		op->SetDatabase(Database);
   280 		
   281 		IPreparedQuery *pQuery = Database->PrepareQuery(
   282 				"CREATE TABLE IF NOT EXISTS sm_cookies \
   283 				( \
   284 					id INTEGER unsigned NOT NULL auto_increment, \
   285 					name varchar(30) NOT NULL UNIQUE, \
   286 					description varchar(255), \
   287 					access INTEGER, \
   288 					PRIMARY KEY (id) \
   289 				)",
   290 				error, sizeof(error), &errCode);
   291 
   292 		if (pQuery == NULL)
   293 		{
   294 			g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookies: %s (%i)", error, errCode);
   295 			return;
   296 		}
   297 
   298 		op->SetCustomPreparedQuery(pQuery);
   299 
   300 		dbi->AddToThreadQueue(op, PrioQueue_High);
   301 
   302 		op = new TQueryOp(Query_CreateTable, 0);
   303 		op->SetDatabase(Database);
   304 			
   305 		pQuery = Database->PrepareQuery(
   306 				"CREATE TABLE IF NOT EXISTS sm_cookie_cache \
   307 				( \
   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) \
   313 				)",
   314 				error, sizeof(error), &errCode);
   315 
   316 		if (pQuery == NULL)
   317 		{
   318 			g_pSM->LogMessage(myself, "Failed to prepare query CreateTable sm_cookie_cache: %s (%i)", error, errCode);
   319 			return;
   320 		}
   321 
   322 		op->SetCustomPreparedQuery(pQuery);
   323 
   324 		dbi->AddToThreadQueue(op, PrioQueue_High);
   325 	}
   326 	else
   327 	{
   328 		g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier);
   329 		Database->Close();
   330 		Database = NULL;
   331 		databaseLoading = false;
   332 		ProcessQueryCache();
   333 		return;
   334 	}
   335 
   336 	if (driver == DRIVER_MYSQL)
   337 	{
   338 		InsertCookieQuery = Database->PrepareQuery(
   339 				"INSERT IGNORE INTO sm_cookies(name, description, access) \
   340 				VALUES(?, ?, ?)",
   341 				error, sizeof(error), &errCode);
   342 
   343 		if (InsertCookieQuery == NULL)
   344 		{
   345 			g_pSM->LogMessage(myself, "Failed to prepare query InsertCookie: %s (%i)", error, errCode);
   346 			return;
   347 		}
   348 
   349 		InsertDataQuery = Database->PrepareQuery(
   350 				"INSERT INTO sm_cookie_cache(player, cookie_id, value, timestamp) \
   351 				VALUES(?, ?, ?, ?) \
   352 				ON DUPLICATE KEY UPDATE value = ?, timestamp = ?",
   353 				error, sizeof(error), &errCode);
   354 
   355 		if (InsertDataQuery == NULL)
   356 		{
   357 			g_pSM->LogMessage(myself, "Failed to prepare query InsertData: %s (%i)", error, errCode);
   358 			return;
   359 		}
   360 	}
   361 	else
   362 	{
   363 		InsertCookieQuery = Database->PrepareQuery(
   364 				"INSERT OR IGNORE INTO sm_cookies(name, description, access) \
   365 				VALUES(?, ?, ?)", 
   366 				error, sizeof(error), &errCode);
   367 
   368 		if (InsertCookieQuery == NULL)
   369 		{
   370 			g_pSM->LogMessage(myself, "Failed to prepare query InsertCookie: %s (%i)", error, errCode);
   371 			return;
   372 		}
   373 
   374 		InsertDataQuery = Database->PrepareQuery(
   375 				"INSERT OR REPLACE INTO sm_cookie_cache(player, cookie_id, value, timestamp) \
   376 				VALUES(?, ?, ?, ?)", 
   377 				error, sizeof(error), &errCode);
   378 
   379 		if (InsertDataQuery == NULL)
   380 		{
   381 			g_pSM->LogMessage(myself, "Failed to prepare query InsertData: %s (%i)", error, errCode);
   382 			return;
   383 		}
   384 	}
   385 
   386 	SelectDataQuery = Database->PrepareQuery(
   387 			"SELECT sm_cookies.name, sm_cookie_cache.value, sm_cookies.description, sm_cookies.access \
   388 			FROM sm_cookies \
   389 			JOIN sm_cookie_cache \
   390 			ON sm_cookies.id = sm_cookie_cache.cookie_id \
   391 			WHERE player = ?",
   392 			error, sizeof(error), &errCode);
   393 
   394 	if (SelectDataQuery == NULL)
   395 	{
   396 		g_pSM->LogMessage(myself, "Failed to prepare query SelectData: %s (%i)", error, errCode);
   397 		return;
   398 	}
   399 
   400 	SelectIdQuery = Database->PrepareQuery(
   401 			"SELECT id \
   402 			FROM sm_cookies \
   403 			WHERE name=?",
   404 			error, sizeof(error), &errCode);
   405 
   406 	if (SelectIdQuery == NULL)
   407 	{
   408 		g_pSM->LogMessage(myself, "Failed to prepare query SelectId: %s (%i)", error, errCode);
   409 		return;
   410 	}
   411 
   412 	databaseLoading = false;
   413 	cell_t result = 0;
   414 
   415 	ProcessQueryCache();
   416 
   417 	return;
   418 }
   419 
   420 bool ClientPrefs::AddQueryToQueue( TQueryOp *query )
   421 {
   422 	queryMutex->Lock();
   423 
   424 	if (Database == NULL && databaseLoading)
   425 	{
   426 		cachedQueries.push_back(query);
   427 		queryMutex->Unlock();
   428 		return true;
   429 	}
   430 
   431 	queryMutex->Unlock();
   432 
   433 	if (Database)
   434 	{
   435 		query->SetDatabase(Database);
   436 		query->SetPreparedQuery();
   437 		dbi->AddToThreadQueue(query, PrioQueue_Normal);
   438 		return true;
   439 	}
   440 
   441 	query->Destroy();
   442 
   443 	/* If Database is NULL and we're not in the loading phase it must have failed - Can't do much */
   444 	return false;
   445 }
   446 
   447 void ClientPrefs::ProcessQueryCache()
   448 {
   449 	SourceHook::List<TQueryOp *>::iterator iter;
   450 
   451 	queryMutex->Lock();
   452 
   453 	iter = cachedQueries.begin();
   454 	
   455 	while (iter != cachedQueries.end())
   456 	{
   457 		TQueryOp *op = (TQueryOp *)*iter;
   458 
   459 		if (Database != NULL)
   460 		{
   461 			op->SetDatabase(Database);
   462 			op->SetPreparedQuery();
   463 			dbi->AddToThreadQueue(op, PrioQueue_Normal);
   464 		}
   465 		else
   466 		{
   467 			delete op;
   468 		}
   469 
   470 		iter++;
   471 	}
   472 
   473 	cachedQueries.clear();
   474 
   475 	queryMutex->Unlock();
   476 }
   477 
   478 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
   479 {
   480 	va_list ap;
   481 	va_start(ap, fmt);
   482 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
   483 	va_end(ap);
   484 
   485 	if (len >= maxlength)
   486 	{
   487 		buffer[maxlength - 1] = '\0';
   488 		return (maxlength - 1);
   489 	}
   490 	else
   491 	{
   492 		return len;
   493 	}
   494 }
   495 
   496 bool Translate(char *buffer, 
   497 				   size_t maxlength,
   498 				   const char *format,
   499 				   unsigned int numparams,
   500 				   size_t *pOutLength,
   501 				   ...)
   502 {
   503 	va_list ap;
   504 	unsigned int i;
   505 	const char *fail_phrase;
   506 	void *params[MAX_TRANSLATE_PARAMS];
   507 
   508 	if (numparams > MAX_TRANSLATE_PARAMS)
   509 	{
   510 		assert(false);
   511 		return false;
   512 	}
   513 
   514 	va_start(ap, pOutLength);
   515 	for (i = 0; i < numparams; i++)
   516 	{
   517 		params[i] = va_arg(ap, void *);
   518 	}
   519 	va_end(ap);
   520 
   521 	if (!g_ClientPrefs.phrases->FormatString(buffer,
   522 		maxlength, 
   523 		format, 
   524 		params,
   525 		numparams,
   526 		pOutLength,
   527 		&fail_phrase))
   528 	{
   529 		if (fail_phrase != NULL)
   530 		{
   531 			g_pSM->LogError(myself, "[SM] Could not find core phrase: %s", fail_phrase);
   532 		}
   533 		else
   534 		{
   535 			g_pSM->LogError(myself, "[SM] Unknown fatal error while translating a core phrase.");
   536 		}
   537 
   538 		return false;
   539 	}
   540 
   541 	return true;
   542 }
   543 
   544 
   545