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:
dvander@2338: if (!Database->DoSimpleQuery(
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 \
dvander@2338: )"))
pred@2220: {
dvander@2338: g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError());
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2220:
dvander@2338: if (!Database->DoSimpleQuery(
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) \
dvander@2338: )"))
pred@2220: {
dvander@2338: g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError());
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2207: }
pred@2207: else if (strcmp(identifier, "mysql") == 0)
pred@2207: {
pred@2207: driver = DRIVER_MYSQL;
pred@2207:
dvander@2338: if (!Database->DoSimpleQuery(
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) \
dvander@2338: )"))
pred@2220: {
dvander@2338: g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError());
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2220:
dvander@2338: if (!Database->DoSimpleQuery(
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) \
dvander@2338: )"))
pred@2220: {
dvander@2338: g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError());
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2207: }
pred@2207: else
pred@2207: {
pred@2207: g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier);
dvander@2338: goto fatal_fail;
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);
dvander@2338: goto fatal_fail;
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);
dvander@2338: goto fatal_fail;
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);
dvander@2338: goto fatal_fail;
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);
dvander@2338: goto fatal_fail;
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);
dvander@2338: goto fatal_fail;
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);
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2220:
pred@2207: databaseLoading = false;
pred@2207:
pred@2207: ProcessQueryCache();
pred@2207:
pred@2207: return;
dvander@2338:
dvander@2338: fatal_fail:
dvander@2338: if (SelectIdQuery != NULL)
dvander@2338: {
dvander@2338: SelectIdQuery->Destroy();
dvander@2338: SelectIdQuery = NULL;
dvander@2338: }
dvander@2338: if (SelectDataQuery != NULL)
dvander@2338: {
dvander@2338: SelectDataQuery->Destroy();
dvander@2338: SelectDataQuery = NULL;
dvander@2338: }
dvander@2338: if (InsertCookieQuery != NULL)
dvander@2338: {
dvander@2338: InsertCookieQuery->Destroy();
dvander@2338: InsertCookieQuery = NULL;
dvander@2338: }
dvander@2338: if (InsertDataQuery != NULL)
dvander@2338: {
dvander@2338: InsertDataQuery->Destroy();
dvander@2338: InsertDataQuery = NULL;
dvander@2338: }
dvander@2338: Database->Close();
dvander@2338: Database = NULL;
dvander@2338: databaseLoading = false;
dvander@2338: ProcessQueryCache();
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();
dvander@2359: if (query->GetQuery() == NULL && query->GetType() != Query_Connect)
dvander@2359: {
dvander@2359: g_pSM->LogError(myself, "Invalid aqtq query %d being inserted! glorp: %p, %p",
dvander@2359: query->GetType(),
dvander@2359: query->GetDriver(),
dvander@2359: query);
dvander@2359: query->Destroy();
dvander@2359: }
dvander@2359: else
dvander@2359: {
dvander@2359: dbi->AddToThreadQueue(query, PrioQueue_Normal);
dvander@2359: }
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: {
dvander@2359: TQueryOp *op = *iter;
pred@2207:
pred@2207: if (Database != NULL)
pred@2207: {
pred@2207: op->SetDatabase(Database);
pred@2207: op->SetPreparedQuery();
dvander@2359: if (op->GetQuery() == NULL && op->GetType() != Query_Connect)
dvander@2359: {
dvander@2359: g_pSM->LogError(myself, "Invalid pqc query %d being inserted! glorp: %p, %p",
dvander@2359: op->GetType(),
dvander@2359: op->GetDriver(),
dvander@2359: op);
dvander@2359: op->Destroy();
dvander@2359: }
dvander@2359: else
dvander@2359: {
dvander@2359: dbi->AddToThreadQueue(op, PrioQueue_Normal);
dvander@2359: }
pred@2207: }
pred@2207: else
pred@2207: {
dvander@2359: op->Destroy();
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: