From e64e2534eb03fb6e2d7bc18d28be8faadeac8815 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Tue, 15 Jul 2008 00:24:08 +0000 Subject: [PATCH] Fixed amb1810 - Clientprefs no longer blocks load. --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%402418 --- extensions/clientprefs/cookie.cpp | 83 +++--- extensions/clientprefs/cookie.h | 18 +- extensions/clientprefs/extension.cpp | 331 ++++++++++++++++------ extensions/clientprefs/extension.h | 22 +- extensions/clientprefs/natives.cpp | 55 ++++ extensions/clientprefs/query.cpp | 242 +++++++++++++--- extensions/clientprefs/query.h | 51 +++- extensions/clientprefs/sdk/smsdk_config.h | 2 +- plugins/clientprefs.sp | 2 +- plugins/include/clientprefs.inc | 8 + plugins/testsuite/clientprefstest.sp | 2 + 11 files changed, 625 insertions(+), 191 deletions(-) diff --git a/extensions/clientprefs/cookie.cpp b/extensions/clientprefs/cookie.cpp index 9f8430c9..1e36cd29 100644 --- a/extensions/clientprefs/cookie.cpp +++ b/extensions/clientprefs/cookie.cpp @@ -80,7 +80,17 @@ void CookieManager::Unload() continue; } - delete current; + g_ClientPrefs.cookieMutex->Lock(); + if (current->usedInQuery) + { + current->shouldDelete = true; + g_ClientPrefs.cookieMutex->Unlock(); + } + else + { + g_ClientPrefs.cookieMutex->Unlock(); + delete current; + } _iter = cookieList.erase(_iter); } @@ -98,7 +108,6 @@ Cookie *CookieManager::FindCookie(const char *name) return *pCookie; } - Cookie *CookieManager::CreateCookie(const char *name, const char *description, CookieAccess access) { Cookie *pCookie = FindCookie(name); @@ -120,25 +129,16 @@ Cookie *CookieManager::CreateCookie(const char *name, const char *description, C cookieTrie.insert(name, pCookie); cookieList.push_back(pCookie); - char quotedname[2 * MAX_NAME_LENGTH + 1]; - char quoteddesc[2 * MAX_DESC_LENGTH + 1]; - - g_ClientPrefs.Database->QuoteString(pCookie->name, quotedname, sizeof(quotedname), NULL); - g_ClientPrefs.Database->QuoteString(pCookie->description, quoteddesc, sizeof(quoteddesc), NULL); - /* Attempt to insert cookie into the db and get its ID num */ - char query[300]; - if (driver == DRIVER_SQLITE) - { - UTIL_Format(query, sizeof(query), "INSERT OR IGNORE INTO sm_cookies(name, description, access) VALUES('%s', '%s', %i)", quotedname, quoteddesc, access); - } - else - { - UTIL_Format(query, sizeof(query), "INSERT IGNORE INTO sm_cookies(name, description, access) VALUES('%s', '%s', %i)", quotedname, quoteddesc, access); - } - TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, query, Query_InsertCookie, pCookie); - dbi->AddToThreadQueue(op, PrioQueue_Normal); + TQueryOp *op = new TQueryOp(Query_InsertCookie, pCookie); + + g_ClientPrefs.cookieMutex->Lock(); + op->m_params.cookie = pCookie; + pCookie->usedInQuery++; + g_ClientPrefs.cookieMutex->Unlock(); + + g_ClientPrefs.AddQueryToQueue(op); return pCookie; } @@ -198,11 +198,10 @@ void CookieManager::OnClientAuthorized(int client, const char *authstring) { connected[client] = true; - char query[300]; - /* Assume that the authstring doesn't need to be quoted */ - UTIL_Format(query, sizeof(query), "SELECT sm_cookies.name, sm_cookie_cache.value, sm_cookies.description, sm_cookies.access FROM sm_cookies JOIN sm_cookie_cache ON sm_cookies.id = sm_cookie_cache.cookie_id WHERE player = '%s'", authstring); - TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, query, Query_SelectData, client); - dbi->AddToThreadQueue(op, PrioQueue_Normal); + TQueryOp *op = new TQueryOp(Query_SelectData, client); + strcpy(op->m_params.steamId, authstring); + + g_ClientPrefs.AddQueryToQueue(op); } void CookieManager::OnClientDisconnecting(int client) @@ -245,24 +244,18 @@ void CookieManager::OnClientDisconnecting(int client) return; } - char quotedvalue[2 * MAX_VALUE_LENGTH + 1]; - g_ClientPrefs.Database->QuoteString(current->value, quotedvalue, sizeof(quotedvalue), NULL); + TQueryOp *op = new TQueryOp(Query_InsertData, client); - char query[300]; - if (driver == DRIVER_SQLITE) - { - UTIL_Format(query, sizeof(query), "INSERT OR REPLACE INTO sm_cookie_cache(player,cookie_id, value, timestamp) VALUES('%s', %i, '%s', %i)", player->GetAuthString(), dbId, quotedvalue, time(NULL)); - } - else - { - UTIL_Format(query, sizeof(query), "INSERT INTO sm_cookie_cache(player,cookie_id, value, timestamp) VALUES('%s', %i, '%s', %i) ON DUPLICATE KEY UPDATE value = '%s', timestamp = %i", player->GetAuthString(), dbId, quotedvalue, time(NULL), quotedvalue, time(NULL)); - } - - TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, query, Query_InsertData, client); - dbi->AddToThreadQueue(op, PrioQueue_Normal); + strcpy(op->m_params.steamId, player->GetAuthString()); + op->m_params.cookieId = dbId; + op->m_params.data = current; + + g_ClientPrefs.AddQueryToQueue(op); current->parent->data[client] = NULL; - delete current; + + + /* We don't delete here, it will be removed when the query is completed */ _iter = clientData[client].erase(_iter); } @@ -330,14 +323,10 @@ void CookieManager::InsertCookieCallback(Cookie *pCookie, int dbId) return; } - char quotedname[2 * MAX_NAME_LENGTH + 1]; - g_ClientPrefs.Database->QuoteString(pCookie->name, quotedname, sizeof(quotedname), NULL); - - char query[300]; - UTIL_Format(query, sizeof(query), "SELECT id FROM sm_cookies WHERE name='%s'", quotedname); - - TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, query, Query_SelectId, pCookie); - dbi->AddToThreadQueue(op, PrioQueue_Normal); + TQueryOp *op = new TQueryOp(Query_SelectId, pCookie); + /* Put the cookie name into the steamId field to save space - Make sure we remember that it's there */ + strcpy(op->m_params.steamId, pCookie->name); + g_ClientPrefs.AddQueryToQueue(op); } void CookieManager::SelectIdCallback(Cookie *pCookie, IQuery *data) diff --git a/extensions/clientprefs/cookie.h b/extensions/clientprefs/cookie.h index 90bff36d..361b746a 100644 --- a/extensions/clientprefs/cookie.h +++ b/extensions/clientprefs/cookie.h @@ -80,6 +80,9 @@ struct Cookie { data[i] = NULL; } + + shouldDelete = false; + usedInQuery = 0; } ~Cookie() @@ -98,6 +101,10 @@ struct Cookie int dbid; CookieData *data[MAXCLIENTS+1]; CookieAccess access; + + /* Reference counting stuff */ + bool shouldDelete; + int usedInQuery; }; class CookieManager : public IClientListener, public IPluginsListener @@ -123,14 +130,13 @@ public: bool AreClientCookiesCached(int client); - IForward *cookieDataLoadedForward; - - SourceHook::List cookieList; - - IBaseMenu *clientMenu; - void OnPluginDestroyed(IPlugin *plugin); +public: + IForward *cookieDataLoadedForward; + SourceHook::List cookieList; + IBaseMenu *clientMenu; + private: KTrie cookieTrie; SourceHook::List clientData[MAXCLIENTS]; diff --git a/extensions/clientprefs/extension.cpp b/extensions/clientprefs/extension.cpp index 303be504..c4e09d74 100644 --- a/extensions/clientprefs/extension.cpp +++ b/extensions/clientprefs/extension.cpp @@ -50,8 +50,11 @@ CookieIteratorHandler g_CookieIteratorHandler; int driver = 0; bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late) -{ - const DatabaseInfo *DBInfo = dbi->FindDatabaseConf("clientprefs"); +{ + queryMutex = threader->MakeMutex(); + cookieMutex = threader->MakeMutex(); + + DBInfo = dbi->FindDatabaseConf("clientprefs"); if (DBInfo == NULL) { @@ -62,7 +65,6 @@ bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late) snprintf(error, maxlength, "Could not find \"clientprefs\" or \"default\" database configs"); return false; } - } if (DBInfo->driver[0] != '\0') @@ -80,92 +82,13 @@ bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late) return false; } - Database = Driver->Connect(DBInfo, true, error, maxlength); - - if (Database == NULL) - { - return false; - } + Database = NULL; + databaseLoading = true; + TQueryOp *op = new TQueryOp(Query_Connect, 0); + dbi->AddToThreadQueue(op, PrioQueue_High); dbi->AddDependency(myself, Driver); - const char *identifier = Driver->GetIdentifier(); - - if (strcmp(identifier, "sqlite") == 0) - { - driver = DRIVER_SQLITE; - - TQueryOp *op = new TQueryOp( - Database, - "CREATE TABLE IF NOT EXISTS sm_cookies \ - ( \ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - name varchar(30) NOT NULL UNIQUE, \ - description varchar(255), \ - access INTEGER \ - )", - Query_CreateTable, - 0); - - dbi->AddToThreadQueue(op, PrioQueue_Normal); - - op = new TQueryOp( - Database, - "CREATE TABLE IF NOT EXISTS sm_cookie_cache \ - ( \ - player varchar(65) NOT NULL, \ - cookie_id int(10) NOT NULL, \ - value varchar(100), \ - timestamp int, \ - PRIMARY KEY (player, cookie_id) \ - )", - Query_CreateTable, - 0); - - dbi->AddToThreadQueue(op, PrioQueue_Normal); - } - else if (strcmp(identifier, "mysql") == 0) - { - driver = DRIVER_MYSQL; - - TQueryOp *op = new TQueryOp( - Database, - "CREATE TABLE IF NOT EXISTS sm_cookies \ - ( \ - id INTEGER unsigned NOT NULL auto_increment, \ - name varchar(30) NOT NULL UNIQUE, \ - description varchar(255), \ - access INTEGER, \ - PRIMARY KEY (id) \ - )", - Query_CreateTable, - 0); - - dbi->AddToThreadQueue(op, PrioQueue_Normal); - - op = new TQueryOp( - Database, - "CREATE TABLE IF NOT EXISTS sm_cookie_cache \ - ( \ - player varchar(65) NOT NULL, \ - cookie_id int(10) NOT NULL, \ - value varchar(100), \ - timestamp int NOT NULL, \ - PRIMARY KEY (player, cookie_id) \ - )", - Query_CreateTable, - 0); - - dbi->AddToThreadQueue(op, PrioQueue_Normal); - } - else - { - snprintf(error, maxlength, "Unsupported driver \"%s\"", identifier); - return false; - } - - - sharesys->AddNatives(myself, g_ClientPrefNatives); sharesys->RegisterLibrary(myself, "clientprefs"); g_CookieManager.cookieDataLoadedForward = forwards->CreateForward("OnClientCookiesCached", ET_Ignore, 1, NULL, Param_Cell); @@ -218,6 +141,16 @@ void ClientPrefs::NotifyInterfaceDrop(SMInterface *pInterface) { if (Database != NULL && (void *)pInterface == (void *)(Database->GetDriver())) { + InsertCookieQuery->Destroy(); + SelectDataQuery->Destroy(); + SelectIdQuery->Destroy(); + InsertDataQuery->Destroy(); + + InsertCookieQuery = NULL; + SelectDataQuery = NULL; + SelectIdQuery = NULL; + InsertDataQuery = NULL; + Database->Close(); Database = NULL; } @@ -243,6 +176,232 @@ void ClientPrefs::SDK_OnUnload() plsys->RemovePluginsListener(&g_CookieManager); playerhelpers->RemoveClientListener(&g_CookieManager); + + /* Kill all our prepared queries - Queries are guaranteed to be flushed before this is called */ + + if (InsertCookieQuery != NULL) + { + InsertCookieQuery->Destroy(); + } + + if (SelectDataQuery != NULL) + { + SelectDataQuery->Destroy(); + } + + if (SelectIdQuery != NULL) + { + SelectIdQuery->Destroy(); + } + + if (InsertDataQuery != NULL) + { + InsertDataQuery->Destroy(); + } + + queryMutex->DestroyThis(); + cookieMutex->DestroyThis(); +} + +void ClientPrefs::DatabaseConnect() +{ + char error[256]; + int errCode = 0; + + Database = Driver->Connect(DBInfo, true, error, sizeof(error)); + + if (Database == NULL) + { + g_pSM->LogError(myself, error); + databaseLoading = false; + ProcessQueryCache(); + return; + } + + const char *identifier = Driver->GetIdentifier(); + + if (strcmp(identifier, "sqlite") == 0) + { + driver = DRIVER_SQLITE; + + TQueryOp *op = new TQueryOp(Query_CreateTable, 0); + + op->SetDatabase(Database); + op->SetCustomPreparedQuery + (Database->PrepareQuery( + "CREATE TABLE IF NOT EXISTS sm_cookies \ + ( \ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + name varchar(30) NOT NULL UNIQUE, \ + description varchar(255), \ + access INTEGER \ + )", + error, sizeof(error), &errCode)); + + dbi->AddToThreadQueue(op, PrioQueue_High); + + op = new TQueryOp(Query_CreateTable, 0); + op->SetDatabase(Database); + op->SetCustomPreparedQuery + (Database->PrepareQuery( + "CREATE TABLE IF NOT EXISTS sm_cookie_cache \ + ( \ + player varchar(65) NOT NULL, \ + cookie_id int(10) NOT NULL, \ + value varchar(100), \ + timestamp int, \ + PRIMARY KEY (player, cookie_id) \ + )", + error, sizeof(error), &errCode)); + + dbi->AddToThreadQueue(op, PrioQueue_High); + } + else if (strcmp(identifier, "mysql") == 0) + { + driver = DRIVER_MYSQL; + + TQueryOp *op = new TQueryOp(Query_CreateTable, 0); + op->SetDatabase(Database); + op->SetCustomPreparedQuery + (Database->PrepareQuery( + "CREATE TABLE IF NOT EXISTS sm_cookies \ + ( \ + id INTEGER unsigned NOT NULL auto_increment, \ + name varchar(30) NOT NULL UNIQUE, \ + description varchar(255), \ + access INTEGER, \ + PRIMARY KEY (id) \ + )", + error, sizeof(error), &errCode)); + + dbi->AddToThreadQueue(op, PrioQueue_High); + + op = new TQueryOp(Query_CreateTable, 0); + op->SetDatabase(Database); + op->SetCustomPreparedQuery + (Database->PrepareQuery( + "CREATE TABLE IF NOT EXISTS sm_cookie_cache \ + ( \ + player varchar(65) NOT NULL, \ + cookie_id int(10) NOT NULL, \ + value varchar(100), \ + timestamp int NOT NULL, \ + PRIMARY KEY (player, cookie_id) \ + )", + error, sizeof(error), &errCode)); + + dbi->AddToThreadQueue(op, PrioQueue_High); + } + else + { + g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier); + Database->Close(); + Database = NULL; + databaseLoading = false; + ProcessQueryCache(); + return; + } + + if (driver == DRIVER_MYSQL) + { + InsertCookieQuery = Database->PrepareQuery( + "INSERT IGNORE INTO sm_cookies(name, description, access) \ + VALUES(?, ?, ?)", + error, sizeof(error), &errCode); + InsertDataQuery = Database->PrepareQuery( + "INSERT INTO sm_cookie_cache(player, cookie_id, value, timestamp) \ + VALUES(?, ?, ?, ?) \ + ON DUPLICATE KEY UPDATE value = ?, timestamp = ?", + error, sizeof(error), &errCode); + } + else + { + InsertCookieQuery = Database->PrepareQuery( + "INSERT OR IGNORE INTO sm_cookies(name, description, access) \ + VALUES(?, ?, ?)", + error, sizeof(error), &errCode); + InsertDataQuery = Database->PrepareQuery( + "INSERT OR REPLACE INTO sm_cookie_cache(player, cookie_id, value, timestamp) \ + VALUES(?, ?, ?, ?)", + error, sizeof(error), &errCode); + } + + SelectDataQuery = Database->PrepareQuery( + "SELECT sm_cookies.name, sm_cookie_cache.value, sm_cookies.description, sm_cookies.access \ + FROM sm_cookies \ + JOIN sm_cookie_cache \ + ON sm_cookies.id = sm_cookie_cache.cookie_id \ + WHERE player = ?", + error, sizeof(error), &errCode); + + SelectIdQuery = Database->PrepareQuery( + "SELECT id \ + FROM sm_cookies \ + WHERE name=?", + error, sizeof(error), &errCode); + + databaseLoading = false; + cell_t result = 0; + + ProcessQueryCache(); + + return; +} + +bool ClientPrefs::AddQueryToQueue( TQueryOp *query ) +{ + queryMutex->Lock(); + + if (Database == NULL && databaseLoading) + { + cachedQueries.push_back(query); + queryMutex->Unlock(); + return true; + } + + queryMutex->Unlock(); + + if (Database) + { + query->SetDatabase(Database); + query->SetPreparedQuery(); + dbi->AddToThreadQueue(query, PrioQueue_Normal); + return true; + } + + /* If Database is NULL and we're not in the loading phase it must have failed - Can't do much */ + return false; +} + +void ClientPrefs::ProcessQueryCache() +{ + SourceHook::List::iterator iter; + + queryMutex->Lock(); + + iter = cachedQueries.begin(); + + while (iter != cachedQueries.end()) + { + TQueryOp *op = (TQueryOp *)*iter; + + if (Database != NULL) + { + op->SetDatabase(Database); + op->SetPreparedQuery(); + dbi->AddToThreadQueue(op, PrioQueue_Normal); + } + else + { + delete op; + } + + iter++; + } + + cachedQueries.clear(); + + queryMutex->Unlock(); } size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) diff --git a/extensions/clientprefs/extension.h b/extensions/clientprefs/extension.h index c166cadb..29800e07 100644 --- a/extensions/clientprefs/extension.h +++ b/extensions/clientprefs/extension.h @@ -47,6 +47,8 @@ #define MAX_TRANSLATE_PARAMS 32 +class TQueryOp; + /** * @brief Sample implementation of the SDK Extension. * Note: Uncomment one of the pre-defined virtual functions in order to use it. @@ -79,6 +81,11 @@ public: virtual void NotifyInterfaceDrop(SMInterface *pInterface); + void DatabaseConnect(); + + bool AddQueryToQueue(TQueryOp *query); + void ProcessQueryCache(); + /** * @brief Called when the pause state is changed. */ @@ -128,7 +135,20 @@ public: public: IDBDriver *Driver; IDatabase *Database; + bool databaseLoading; IPhraseCollection *phrases; + const DatabaseInfo *DBInfo; + + IPreparedQuery *InsertCookieQuery; + IPreparedQuery *SelectDataQuery; + IPreparedQuery *InsertDataQuery; + IPreparedQuery *SelectIdQuery; + + IMutex *cookieMutex; + +private: + SourceHook::List cachedQueries; + IMutex *queryMutex; }; class CookieTypeHandler : public IHandleTypeDispatch @@ -136,7 +156,7 @@ class CookieTypeHandler : public IHandleTypeDispatch public: void OnHandleDestroy(HandleType_t type, void *object) { - /* No delete needed since Cookies are persistant */ + /* No delete needed since Cookies are persistent */ } }; diff --git a/extensions/clientprefs/natives.cpp b/extensions/clientprefs/natives.cpp index d347015c..2060c034 100644 --- a/extensions/clientprefs/natives.cpp +++ b/extensions/clientprefs/natives.cpp @@ -35,6 +35,11 @@ cell_t RegClientPrefCookie(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + char *name; pContext->LocalToString(params[1], &name); @@ -62,6 +67,11 @@ cell_t RegClientPrefCookie(IPluginContext *pContext, const cell_t *params) cell_t FindClientPrefCookie(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + char *name; pContext->LocalToString(params[1], &name); @@ -81,6 +91,11 @@ cell_t FindClientPrefCookie(IPluginContext *pContext, const cell_t *params) cell_t SetClientPrefCookie(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + int client = params[1]; if ((client < 1) || (client > playerhelpers->GetMaxClients())) @@ -111,6 +126,11 @@ cell_t SetClientPrefCookie(IPluginContext *pContext, const cell_t *params) cell_t GetClientPrefCookie(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + int client = params[1]; if ((client < 1) || (client > playerhelpers->GetMaxClients())) @@ -144,6 +164,11 @@ cell_t GetClientPrefCookie(IPluginContext *pContext, const cell_t *params) cell_t AreClientCookiesCached(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + int client = params[1]; if ((client < 1) || (client > playerhelpers->GetMaxClients())) @@ -156,6 +181,11 @@ cell_t AreClientCookiesCached(IPluginContext *pContext, const cell_t *params) cell_t GetCookieAccess(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + Handle_t hndl = static_cast(params[1]); HandleError err; HandleSecurity sec; @@ -176,6 +206,11 @@ cell_t GetCookieAccess(IPluginContext *pContext, const cell_t *params) static cell_t GetCookieIterator(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + SourceHook::List::iterator *iter = new SourceHook::List::iterator; *iter = g_CookieManager.cookieList.begin(); @@ -190,6 +225,11 @@ static cell_t GetCookieIterator(IPluginContext *pContext, const cell_t *params) static cell_t ReadCookieIterator(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + SourceHook::List::iterator *iter; Handle_t hndl = static_cast(params[1]); @@ -226,6 +266,11 @@ static cell_t ReadCookieIterator(IPluginContext *pContext, const cell_t *params) cell_t ShowSettingsMenu(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + char message[256]; Translate(message, sizeof(message), "%T:", 2, NULL, "Client Settings", ¶ms[1]); @@ -237,6 +282,11 @@ cell_t ShowSettingsMenu(IPluginContext *pContext, const cell_t *params) cell_t AddSettingsMenuItem(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + char *display; pContext->LocalToString(params[3], &display); @@ -278,6 +328,11 @@ cell_t AddSettingsMenuItem(IPluginContext *pContext, const cell_t *params) cell_t AddSettingsPrefabMenuItem(IPluginContext *pContext, const cell_t *params) { + if (g_ClientPrefs.Database == NULL && !g_ClientPrefs.databaseLoading) + { + return pContext->ThrowNativeError("Clientprefs is disabled due to a failed database connection"); + } + Handle_t hndl = static_cast(params[1]); HandleError err; HandleSecurity sec; diff --git a/extensions/clientprefs/query.cpp b/extensions/clientprefs/query.cpp index 0016a93d..053bd087 100644 --- a/extensions/clientprefs/query.cpp +++ b/extensions/clientprefs/query.cpp @@ -36,52 +36,83 @@ void TQueryOp::RunThinkPart() { //handler for threaded sql queries + if (m_type == Query_Connect) + { + return; + } + if (m_pQuery) { switch (m_type) { - case Query_InsertCookie: - g_CookieManager.InsertCookieCallback(pCookie, m_insertId); - break; - case Query_SelectData: - g_CookieManager.ClientConnectCallback(m_client, m_pQuery); - break; - case Query_InsertData: - //No specific handling - break; - case Query_SelectId: - g_CookieManager.SelectIdCallback(pCookie, m_pQuery); - break; - default: - break; - } + case Query_InsertCookie: + { + g_CookieManager.InsertCookieCallback(m_pCookie, m_insertId); + break; + } - m_pQuery->Destroy(); - } - else - { - g_pSM->LogError(myself,"Failed SQL Query, Error: \"%s\" (Query id %i - client %i)", error, m_type, m_client); + case Query_SelectData: + { + g_CookieManager.ClientConnectCallback(m_client, m_pQuery); + break; + } + + case Query_SelectId: + { + g_CookieManager.SelectIdCallback(m_pCookie, m_pQuery); + break; + } + + case Query_CreateTable: + { + m_pQuery->Destroy(); + delete m_pQuery; + m_pQuery = NULL; + break; + } + + default: + { + break; + } + } } } void TQueryOp::RunThreadPart() { - m_pDatabase->LockForFullAtomicOperation(); - m_pQuery = m_pDatabase->DoQuery(m_Query.c_str()); - - if (!m_pQuery) + if (m_type == Query_Connect) { - g_pSM->LogError(myself, "Failed SQL Query, Error: \"%s\" (Query id %i - client %i)", m_pDatabase->GetError(), m_type, m_client); + g_ClientPrefs.DatabaseConnect(); } + else + { + if (m_database == NULL) + { + return; + } - m_insertId = g_ClientPrefs.Database->GetInsertID(); + m_database->LockForFullAtomicOperation(); - m_pDatabase->UnlockFromFullAtomicOperation(); + if (!BindParamsAndRun()) + { + g_pSM->LogError(myself, "Failed SQL Query, Error: \"%s\" (Query id %i - client %i)", m_database->GetError(), m_type, m_client); + } + + m_insertId = m_database->GetInsertID(); + + m_database->UnlockFromFullAtomicOperation(); + } } IDBDriver *TQueryOp::GetDriver() { - return m_pDatabase->GetDriver(); + if (m_database == NULL) + { + return NULL; + } + + return m_database->GetDriver(); } IdentityToken_t *TQueryOp::GetOwner() { @@ -92,24 +123,157 @@ void TQueryOp::Destroy() delete this; } -TQueryOp::TQueryOp(IDatabase *db, const char *query, enum querytype type, int client) +TQueryOp::TQueryOp(enum querytype type, int client) { - m_pDatabase = db; - m_Query = query; m_type = type; m_client = client; m_pQuery = NULL; - - m_pDatabase->IncReferenceCount(); + m_database = NULL; } -TQueryOp::TQueryOp(IDatabase *db, const char *query, enum querytype type, Cookie *cookie) +TQueryOp::TQueryOp(enum querytype type, Cookie *cookie) { - m_pDatabase = db; - m_Query = query; m_type = type; - pCookie = cookie; + m_pCookie = cookie; m_pQuery = NULL; - - m_pDatabase->IncReferenceCount(); + m_database = NULL; } + +void TQueryOp::SetDatabase( IDatabase *db ) +{ + m_database = db; + m_database->IncReferenceCount(); +} + +bool TQueryOp::BindParamsAndRun() +{ + if (m_pQuery == NULL) + { + return false; + } + + switch (m_type) + { + case Query_InsertCookie: + { + m_pQuery->BindParamString(0, m_params.cookie->name, false); + m_pQuery->BindParamString(1, m_params.cookie->description, false); + m_pQuery->BindParamInt(2, m_params.cookie->access); + + break; + } + + case Query_SelectData: + { + m_pQuery->BindParamString(0, m_params.steamId, false); + + break; + } + + case Query_InsertData: + { + m_pQuery->BindParamString(0, m_params.steamId, false); + m_pQuery->BindParamInt(1, m_params.cookieId); + m_pQuery->BindParamString(2, m_params.data->value, false); + m_pQuery->BindParamInt(3, (unsigned int)time(NULL), false); + + if (driver == DRIVER_MYSQL) + { + m_pQuery->BindParamString(4, m_params.data->value, false); + m_pQuery->BindParamInt(5, (unsigned int)time(NULL), false); + } + + break; + } + + case Query_SelectId: + { + /* the steamId var was actually used to store the name of the cookie - Save duplicating vars */ + m_pQuery->BindParamString(0, m_params.steamId, false); + + break; + } + + default: + { + break; + } + + } + + return m_pQuery->Execute(); +} + +void TQueryOp::SetPreparedQuery() +{ + switch (m_type) + { + case Query_InsertCookie: + { + m_pQuery = g_ClientPrefs.InsertCookieQuery; + break; + } + + case Query_SelectData: + { + m_pQuery = g_ClientPrefs.SelectDataQuery; + break; + } + + case Query_InsertData: + { + m_pQuery = g_ClientPrefs.InsertDataQuery; + break; + } + + case Query_SelectId: + { + m_pQuery = g_ClientPrefs.SelectIdQuery; + break; + } + + default: + { + break; + } + + } +} + +void TQueryOp::SetCustomPreparedQuery(IPreparedQuery *query) +{ + m_pQuery = query; +} + +ParamData::~ParamData() +{ + if (cookie) + { + g_ClientPrefs.cookieMutex->Lock(); + cookie->usedInQuery--; + + if (cookie->shouldDelete && cookie->usedInQuery <= 0) + { + g_ClientPrefs.cookieMutex->Unlock(); + delete cookie; + cookie = NULL; + } + + g_ClientPrefs.cookieMutex->Unlock(); + } + + if (data) + { + /* Data is only ever passed in a client disconnect query and always needs to be deleted */ + delete data; + data = NULL; + } +} + +ParamData::ParamData() +{ + cookie = NULL; + data = NULL; + steamId[0] = '\0'; + cookieId = 0; +} \ No newline at end of file diff --git a/extensions/clientprefs/query.h b/extensions/clientprefs/query.h index 80f21072..b0c77d84 100644 --- a/extensions/clientprefs/query.h +++ b/extensions/clientprefs/query.h @@ -43,36 +43,67 @@ enum querytype Query_InsertData, Query_SelectId, Query_CreateTable, + Query_Connect, +}; + +struct PreparedQueryWrapper; +struct Cookie; +struct CookieData; +#define MAX_NAME_LENGTH 30 + +/* This stores all the info required for our param binding until the thread is executed */ +struct ParamData +{ + ParamData(); + + ~ParamData(); + + /* Contains a name, description and access for InsertCookie queries */ + Cookie *cookie; + /* A clients steamid - Used for most queries - Doubles as storage for the cookie name*/ + char steamId[MAX_NAME_LENGTH]; + + int cookieId; + CookieData *data; }; class TQueryOp : public IDBThreadOperation { public: - TQueryOp(IDatabase *db, const char *query, enum querytype type, int client); - TQueryOp(IDatabase *db, const char *query, enum querytype type, Cookie *cookie); + TQueryOp(enum querytype type, int client); + TQueryOp(enum querytype type, Cookie *cookie); ~TQueryOp() {} IDBDriver *GetDriver(); IdentityToken_t *GetOwner(); + void SetDatabase(IDatabase *db); + void SetPreparedQuery(); + void SetCustomPreparedQuery(IPreparedQuery *wrapper); + void Destroy(); void RunThreadPart(); /* Thread has been cancelled due to driver unloading. Nothing else to do? */ - void CancelThinkPart() - { - } + void CancelThinkPart() {} void RunThinkPart(); + bool BindParamsAndRun(); + + /* Params to be bound */ + ParamData m_params; + private: - IDatabase *m_pDatabase; - IQuery *m_pQuery; - SourceHook::String m_Query; - char error[255]; + IPreparedQuery *m_pQuery; + IDatabase *m_database; + + /* Query type */ enum querytype m_type; + + /* Data to be passed to the callback */ int m_client; int m_insertId; - Cookie *pCookie; + Cookie *m_pCookie; }; diff --git a/extensions/clientprefs/sdk/smsdk_config.h b/extensions/clientprefs/sdk/smsdk_config.h index d50fe64a..2cd8c6ea 100644 --- a/extensions/clientprefs/sdk/smsdk_config.h +++ b/extensions/clientprefs/sdk/smsdk_config.h @@ -69,7 +69,7 @@ //#define SMEXT_ENABLE_MEMUTILS #define SMEXT_ENABLE_GAMEHELPERS //#define SMEXT_ENABLE_TIMERSYS -//#define SMEXT_ENABLE_THREADER +#define SMEXT_ENABLE_THREADER //#define SMEXT_ENABLE_LIBSYS #define SMEXT_ENABLE_MENUS //#define SMEXT_ENABLE_ADTFACTORY diff --git a/plugins/clientprefs.sp b/plugins/clientprefs.sp index 4e60ed9c..c380fdef 100644 --- a/plugins/clientprefs.sp +++ b/plugins/clientprefs.sp @@ -58,7 +58,7 @@ public Action:Command_Cookie(client, args) { if (args == 0) { - ReplyToCommand(client, "[SM] Usage: sm_cookie [value]"); + ReplyToCommand(client, "[SM] Usage: sm_cookies [value]"); ReplyToCommand(client, "[SM] %t", "Printing Cookie List"); /* Show list of cookies */ diff --git a/plugins/include/clientprefs.inc b/plugins/include/clientprefs.inc index 516eea1b..aa24b04b 100644 --- a/plugins/include/clientprefs.inc +++ b/plugins/include/clientprefs.inc @@ -74,6 +74,14 @@ enum CookieMenuAction CookieMenuAction_SelectOption = 1, }; +/** + * Note: + * + * A successful return value/result on any client prefs native only guarantees that the local cache has been updated. + * Database connection problems can still prevent the data from being permanently saved. Connection problems will be logged as + * errors by the clientprefs extension. + */ + /** * Creates a new Client preference cookie. * diff --git a/plugins/testsuite/clientprefstest.sp b/plugins/testsuite/clientprefstest.sp index f5554d66..45e58165 100644 --- a/plugins/testsuite/clientprefstest.sp +++ b/plugins/testsuite/clientprefstest.sp @@ -44,6 +44,8 @@ public CookieSelected(client, CookieMenuAction:action, any:info, String:buffer[] public bool:OnClientConnect(client, String:rejectmsg[], maxlen) { LogMessage("Connect Cookie state: %s", AreClientCookiesCached(client) ? "YES" : "NO"); + + return true; } public OnClientCookiesCached(client)