extensions/clientprefs/extension.cpp
author David Anderson <dvander@alliedmods.net>
Sat Jan 31 23:42:35 2009 -0500 (2009-01-31)
changeset 2550 a2c8eec6dc22
parent 2373 01dd8a057823
child 2666 18df531a8642
permissions -rw-r--r--
Database fallback for clientprefs is now more "intelligent" (bug 3564, r=pred).
     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 DbDriver g_DriverType;
    49 static const DatabaseInfo *storage_local = NULL;
    50 
    51 bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late)
    52 {
    53 	queryMutex = threader->MakeMutex();
    54 	cookieMutex = threader->MakeMutex();
    55 
    56 	DBInfo = dbi->FindDatabaseConf("clientprefs");
    57 
    58 	if (DBInfo == NULL)
    59 	{
    60 		DBInfo = dbi->FindDatabaseConf("default");
    61 
    62 		if (DBInfo == NULL ||
    63 			(strcmp(DBInfo->host, "localhost") == 0 &&
    64 			 strcmp(DBInfo->database, "sourcemod") == 0 && 
    65 			 strcmp(DBInfo->user, "root") == 0 &&
    66 			 strcmp(DBInfo->pass, "") == 0 &&
    67 			 strcmp(DBInfo->driver, "") == 0))
    68 		{
    69 			storage_local = dbi->FindDatabaseConf("storage-local");
    70 			if (DBInfo == NULL)
    71 			{
    72 				DBInfo = storage_local;
    73 			}
    74 		}
    75 
    76 		if (DBInfo == NULL)
    77 		{
    78 			snprintf(error, maxlength, "Could not find \"clientprefs\" or \"default\" database configs");
    79 			return false;
    80 		}
    81 	}
    82 
    83 	if (DBInfo->driver[0] != '\0')
    84 	{
    85 		Driver = dbi->FindOrLoadDriver(DBInfo->driver);
    86 	}
    87 	else
    88 	{
    89 		Driver = dbi->GetDefaultDriver();
    90 	}
    91 
    92 	if (Driver == NULL)
    93 	{
    94 		snprintf(error, maxlength, "Could not load DB Driver \"%s\"", DBInfo->driver);
    95 		return false;
    96 	}
    97 
    98 	Database = NULL;
    99 	databaseLoading = true;
   100 	TQueryOp *op = new TQueryOp(Query_Connect, 0);
   101 	dbi->AddToThreadQueue(op, PrioQueue_High);
   102 
   103 	dbi->AddDependency(myself, Driver);
   104 
   105 	sharesys->AddNatives(myself, g_ClientPrefNatives);
   106 	sharesys->RegisterLibrary(myself, "clientprefs");
   107 	g_CookieManager.cookieDataLoadedForward = forwards->CreateForward("OnClientCookiesCached", ET_Ignore, 1, NULL, Param_Cell);
   108 
   109 	g_CookieType = handlesys->CreateType("Cookie", 
   110 		&g_CookieTypeHandler, 
   111 		0, 
   112 		NULL, 
   113 		NULL, 
   114 		myself->GetIdentity(), 
   115 		NULL);
   116 
   117 	g_CookieIterator = handlesys->CreateType("CookieIterator", 
   118 		&g_CookieIteratorHandler, 
   119 		0, 
   120 		NULL, 
   121 		NULL, 
   122 		myself->GetIdentity(), 
   123 		NULL);
   124 
   125 	IMenuStyle *style = menus->GetDefaultStyle();
   126 	g_CookieManager.clientMenu = style->CreateMenu(&g_Handler, NULL);
   127 	g_CookieManager.clientMenu->SetDefaultTitle("Client Settings:");
   128 
   129 	plsys->AddPluginsListener(&g_CookieManager);
   130 
   131 	phrases = translator->CreatePhraseCollection();
   132 	phrases->AddPhraseFile("clientprefs.phrases");
   133 	phrases->AddPhraseFile("common.phrases");
   134 
   135 	return true;
   136 }
   137 
   138 void ClientPrefs::SDK_OnAllLoaded()
   139 {
   140 	playerhelpers->AddClientListener(&g_CookieManager);
   141 }
   142 
   143 bool ClientPrefs::QueryInterfaceDrop(SMInterface *pInterface)
   144 {
   145 	if ((void *)pInterface == (void *)(Database->GetDriver()))
   146 	{
   147 		return false;
   148 	}
   149 
   150 	return true;
   151 }
   152 
   153 void ClientPrefs::NotifyInterfaceDrop(SMInterface *pInterface)
   154 {
   155 	if (Database != NULL && (void *)pInterface == (void *)(Database->GetDriver()))
   156 	{
   157 		Database->Close();
   158 		Database = NULL;
   159 	}
   160 }
   161 
   162 void ClientPrefs::SDK_OnUnload()
   163 {
   164 	handlesys->RemoveType(g_CookieType, myself->GetIdentity());
   165 	handlesys->RemoveType(g_CookieIterator, myself->GetIdentity());
   166 
   167 	g_CookieManager.Unload();
   168 
   169 	if (Database != NULL)
   170 	{
   171 		Database->Close();
   172 	}
   173 
   174 	forwards->ReleaseForward(g_CookieManager.cookieDataLoadedForward);
   175 
   176 	g_CookieManager.clientMenu->Destroy();
   177 
   178 	phrases->Destroy();
   179 
   180 	plsys->RemovePluginsListener(&g_CookieManager);
   181 	playerhelpers->RemoveClientListener(&g_CookieManager);
   182 
   183 	queryMutex->DestroyThis();
   184 	cookieMutex->DestroyThis();
   185 }
   186 
   187 void ClientPrefs::DatabaseConnect()
   188 {
   189 	char error[256];
   190 	int errCode = 0;
   191 
   192 	Database = Driver->Connect(DBInfo, true, error, sizeof(error));
   193 
   194 	if (Database == NULL &&
   195 		DBInfo != storage_local &&
   196 		storage_local != NULL)
   197 	{
   198 		DBInfo = storage_local;
   199 		Database = Driver->Connect(DBInfo, true, error, sizeof(error));
   200 	}
   201 
   202 	if (Database == NULL)
   203 	{
   204 		g_pSM->LogError(myself, error);
   205 		databaseLoading = false;
   206 		ProcessQueryCache();
   207 		return;
   208 	}
   209 
   210 	const char *identifier = Driver->GetIdentifier();
   211 
   212 	if (strcmp(identifier, "sqlite") == 0)
   213 	{
   214 		g_DriverType = Driver_SQLite;
   215 
   216 		if (!Database->DoSimpleQuery(
   217 				"CREATE TABLE IF NOT EXISTS sm_cookies  \
   218 				( \
   219 					id INTEGER PRIMARY KEY AUTOINCREMENT, \
   220 					name varchar(30) NOT NULL UNIQUE, \
   221 					description varchar(255), \
   222 					access INTEGER \
   223 				)"))
   224 		{
   225 			g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError());
   226 			goto fatal_fail;
   227 		}
   228 
   229 		if (!Database->DoSimpleQuery(
   230 				"CREATE TABLE IF NOT EXISTS sm_cookie_cache \
   231 				( \
   232 					player varchar(65) NOT NULL, \
   233 					cookie_id int(10) NOT NULL, \
   234 					value varchar(100), \
   235 					timestamp int, \
   236 					PRIMARY KEY (player, cookie_id) \
   237 				)"))
   238 		{
   239 			g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError());
   240 			goto fatal_fail;
   241 		}
   242 	}
   243 	else if (strcmp(identifier, "mysql") == 0)
   244 	{
   245 		g_DriverType = Driver_MySQL;
   246 
   247 		if (!Database->DoSimpleQuery(
   248 				"CREATE TABLE IF NOT EXISTS sm_cookies \
   249 				( \
   250 					id INTEGER unsigned NOT NULL auto_increment, \
   251 					name varchar(30) NOT NULL UNIQUE, \
   252 					description varchar(255), \
   253 					access INTEGER, \
   254 					PRIMARY KEY (id) \
   255 				)"))
   256 		{
   257 			g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError());
   258 			goto fatal_fail;
   259 		}
   260 
   261 		if (!Database->DoSimpleQuery(
   262 				"CREATE TABLE IF NOT EXISTS sm_cookie_cache \
   263 				( \
   264 					player varchar(65) NOT NULL, \
   265 					cookie_id int(10) NOT NULL, \
   266 					value varchar(100), \
   267 					timestamp int NOT NULL, \
   268 					PRIMARY KEY (player, cookie_id) \
   269 				)"))
   270 		{
   271 			g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError());
   272 			goto fatal_fail;
   273 		}
   274 	}
   275 	else
   276 	{
   277 		g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier);
   278 		goto fatal_fail;
   279 	}
   280 
   281 	databaseLoading = false;
   282 
   283 	ProcessQueryCache();
   284 
   285 	return;
   286 
   287 fatal_fail:
   288 	Database->Close();
   289 	Database = NULL;
   290 	databaseLoading = false;
   291 	ProcessQueryCache();
   292 }
   293 
   294 bool ClientPrefs::AddQueryToQueue( TQueryOp *query )
   295 {
   296 	queryMutex->Lock();
   297 
   298 	if (Database == NULL && databaseLoading)
   299 	{
   300 		cachedQueries.push_back(query);
   301 		queryMutex->Unlock();
   302 		return true;
   303 	}
   304 
   305 	queryMutex->Unlock();
   306 
   307 	if (Database)
   308 	{
   309 		query->SetDatabase(Database);
   310 		dbi->AddToThreadQueue(query, PrioQueue_Normal);
   311 		return true;
   312 	}
   313 
   314 	query->Destroy();
   315 
   316 	/* If Database is NULL and we're not in the loading phase it must have failed - Can't do much */
   317 	return false;
   318 }
   319 
   320 void ClientPrefs::ProcessQueryCache()
   321 {
   322 	SourceHook::List<TQueryOp *>::iterator iter;
   323 
   324 	queryMutex->Lock();
   325 
   326 	iter = cachedQueries.begin();
   327 	
   328 	while (iter != cachedQueries.end())
   329 	{
   330 		TQueryOp *op = *iter;
   331 
   332 		if (Database != NULL)
   333 		{
   334 			op->SetDatabase(Database);
   335 			dbi->AddToThreadQueue(op, PrioQueue_Normal);
   336 		}
   337 		else
   338 		{
   339 			op->Destroy();
   340 		}
   341 
   342 		iter++;
   343 	}
   344 
   345 	cachedQueries.clear();
   346 
   347 	queryMutex->Unlock();
   348 }
   349 
   350 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
   351 {
   352 	va_list ap;
   353 	va_start(ap, fmt);
   354 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
   355 	va_end(ap);
   356 
   357 	if (len >= maxlength)
   358 	{
   359 		buffer[maxlength - 1] = '\0';
   360 		return (maxlength - 1);
   361 	}
   362 	else
   363 	{
   364 		return len;
   365 	}
   366 }
   367 
   368 bool Translate(char *buffer, 
   369 				   size_t maxlength,
   370 				   const char *format,
   371 				   unsigned int numparams,
   372 				   size_t *pOutLength,
   373 				   ...)
   374 {
   375 	va_list ap;
   376 	unsigned int i;
   377 	const char *fail_phrase;
   378 	void *params[MAX_TRANSLATE_PARAMS];
   379 
   380 	if (numparams > MAX_TRANSLATE_PARAMS)
   381 	{
   382 		assert(false);
   383 		return false;
   384 	}
   385 
   386 	va_start(ap, pOutLength);
   387 	for (i = 0; i < numparams; i++)
   388 	{
   389 		params[i] = va_arg(ap, void *);
   390 	}
   391 	va_end(ap);
   392 
   393 	if (!g_ClientPrefs.phrases->FormatString(buffer,
   394 		maxlength, 
   395 		format, 
   396 		params,
   397 		numparams,
   398 		pOutLength,
   399 		&fail_phrase))
   400 	{
   401 		if (fail_phrase != NULL)
   402 		{
   403 			g_pSM->LogError(myself, "[SM] Could not find core phrase: %s", fail_phrase);
   404 		}
   405 		else
   406 		{
   407 			g_pSM->LogError(myself, "[SM] Unknown fatal error while translating a core phrase.");
   408 		}
   409 
   410 		return false;
   411 	}
   412 
   413 	return true;
   414 }
   415 
   416 
   417