dvander@1203: /** dvander@1203: * vim: set ts=4 : ds@1330: * ============================================================================= dvander@1226: * SourceMod SQLite Extension ds@1947: * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. ds@1330: * ============================================================================= dvander@1203: * ds@1330: * This program is free software; you can redistribute it and/or modify it under ds@1330: * the terms of the GNU General Public License, version 3.0, as published by the ds@1330: * Free Software Foundation. dvander@1226: * ds@1330: * This program is distributed in the hope that it will be useful, but WITHOUT ds@1330: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ds@1330: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ds@1330: * details. dvander@1226: * ds@1330: * You should have received a copy of the GNU General Public License along with ds@1330: * this program. If not, see . dvander@1226: * ds@1330: * As a special exception, AlliedModders LLC gives you permission to link the ds@1330: * code of this program (as well as its derivative works) to "Half-Life 2," the ds@1330: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software ds@1330: * by the Valve Corporation. You must obey the GNU General Public License in ds@1330: * all respects for all other code used. Additionally, AlliedModders LLC grants ds@1330: * this exception to all derivative works. AlliedModders LLC defines further ds@1330: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), ds@1330: * or . dvander@1203: * dvander@1203: * Version: $Id$ dvander@1203: */ dvander@1203: dvander@1203: #include dvander@1203: #include "extension.h" dvander@1203: #include "SqDriver.h" dvander@1203: #include "SqDatabase.h" dvander@1203: dvander@1203: SqDriver g_SqDriver; dvander@1203: dvander@1203: unsigned int strncopy(char *dest, const char *src, size_t count) dvander@1203: { dvander@1203: if (!count) dvander@1203: { dvander@1203: return 0; dvander@1203: } dvander@1203: dvander@1203: char *start = dest; dvander@1203: while ((*src) && (--count)) dvander@1203: { dvander@1203: *dest++ = *src++; dvander@1203: } dvander@1203: *dest = '\0'; dvander@1203: dvander@1203: return (dest - start); dvander@1203: } dvander@1203: dvander@1447: int busy_handler(void *unused1, int unused2) dvander@1447: { dvander@1447: #if defined PLATFORM_WINDOWS dvander@1447: Sleep(100); dvander@1447: #elif defined PLATFORM_LINUX dvander@1447: usleep(100000); dvander@1447: #endif dvander@1447: return 1; dvander@1447: } dvander@1447: dvander@1203: SqDriver::SqDriver() dvander@1203: { dvander@1203: m_Handle = BAD_HANDLE; dvander@1203: m_pOpenLock = NULL; dvander@1645: m_bThreadSafe = false; dvander@1203: } dvander@1203: dvander@1203: void SqDriver::Initialize() dvander@1203: { dvander@1203: m_pOpenLock = threader->MakeMutex(); dvander@1645: dvander@1645: InitializeThreadSafety(); dvander@1203: } dvander@1203: dvander@1203: void SqDriver::Shutdown() dvander@1203: { dvander@1203: if (m_pOpenLock) dvander@1203: { dvander@1203: m_pOpenLock->DestroyThis(); dvander@1203: } dvander@1645: dvander@1645: if (m_bThreadSafe) dvander@1645: { dvander@1645: sqlite3_enable_shared_cache(0); dvander@1645: } dvander@1203: } dvander@1203: dvander@1203: bool SqDriver::IsThreadSafe() dvander@1203: { dvander@1203: return true; dvander@1203: } dvander@1203: dvander@1203: bool SqDriver::InitializeThreadSafety() dvander@1203: { dvander@1645: if (m_bThreadSafe) dvander@1645: { dvander@1645: return true; dvander@1645: } dvander@1645: dvander@1645: if (sqlite3_threadsafe() == 0) dvander@1645: { dvander@1645: return false; dvander@1645: } dvander@1645: dvander@1645: if (sqlite3_enable_shared_cache(1) != SQLITE_OK) dvander@1645: { dvander@1645: return false; dvander@1645: } dvander@1645: dvander@1645: m_bThreadSafe = true; dvander@1645: dvander@1203: return true; dvander@1203: } dvander@1203: dvander@1203: void SqDriver::ShutdownThreadSafety() dvander@1203: { dvander@1203: return; dvander@1203: } dvander@1203: dvander@1203: IdentityToken_t *SqDriver::GetIdentity() dvander@1203: { dvander@1203: return myself->GetIdentity(); dvander@1203: } dvander@1203: dvander@1203: const char *SqDriver::GetProductName() dvander@1203: { dvander@1203: return "SQLite"; dvander@1203: } dvander@1203: dvander@1203: const char *SqDriver::GetIdentifier() dvander@1203: { dvander@1203: return "sqlite"; dvander@1203: } dvander@1203: dvander@1203: Handle_t SqDriver::GetHandle() dvander@1203: { dvander@1203: if (m_Handle == BAD_HANDLE) dvander@1203: { dvander@1203: m_Handle = dbi->CreateHandle(DBHandle_Driver, this, myself->GetIdentity()); dvander@1203: } dvander@1203: dvander@1203: return m_Handle; dvander@1203: } dvander@1203: dvander@1203: inline bool IsPathSepChar(char c) dvander@1203: { dvander@1203: #if defined PLATFORM_WINDOWS dvander@1203: return (c == '\\' || c == '/'); dvander@2471: #elif defined PLATFORM_LINUX || defined PLATFORM_APPLE dvander@1203: return (c == '/'); dvander@1203: #endif dvander@1203: } dvander@1203: dvander@1203: IDatabase *SqDriver::Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength) dvander@1203: { dvander@1203: /* We wrap most of the open process in a mutex just to be safe */ dvander@1203: m_pOpenLock->Lock(); dvander@1203: dvander@1203: /* Format our path */ dvander@1203: char path[PLATFORM_MAX_PATH]; dvander@1203: size_t len = libsys->PathFormat(path, sizeof(path), "sqlite/%s", info->database); dvander@1203: dvander@1203: /* Chop any filename off */ dvander@1203: for (size_t i = len-1; dvander@1203: i >= 0 && i <= len-1; dvander@1203: i--) dvander@1203: { dvander@1203: if (IsPathSepChar(path[i])) dvander@1203: { dvander@1203: path[i] = '\0'; dvander@1203: break; dvander@1203: } dvander@1203: } dvander@1203: dvander@1203: /* Test the full path */ dvander@1203: char fullpath[PLATFORM_MAX_PATH]; dvander@1203: g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data/%s", path); dvander@1203: if (!libsys->IsPathDirectory(fullpath)) dvander@1203: { dvander@1203: /* Make sure the data folder exists */ dvander@1203: len = g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data"); dvander@1203: if (!libsys->IsPathDirectory(fullpath)) dvander@1203: { dvander@1203: if (!libsys->CreateFolder(fullpath)) dvander@1203: { dvander@1203: strncopy(error, "Could not create or open \"data\" folder\"", maxlength); dvander@1203: m_pOpenLock->Unlock(); dvander@1203: return NULL; dvander@1203: } dvander@1203: } dvander@1203: dvander@1203: /* The data folder exists - create each subdir as needed! */ dvander@1203: char *cur_ptr = path; dvander@1203: dvander@1203: do dvander@1203: { dvander@1203: /* Find the next suitable path */ dvander@1203: char *next_ptr = cur_ptr; dvander@1203: while (*next_ptr != '\0') dvander@1203: { dvander@1203: if (IsPathSepChar(*next_ptr)) dvander@1203: { dvander@1203: *next_ptr = '\0'; dvander@1203: next_ptr++; dvander@1203: break; dvander@1203: } dvander@1203: next_ptr++; dvander@1203: } dvander@1203: if (*next_ptr == '\0') dvander@1203: { dvander@1203: next_ptr = NULL; dvander@1203: } dvander@1203: len += libsys->PathFormat(&fullpath[len], sizeof(fullpath)-len, "/%s", cur_ptr); dvander@1203: if (!libsys->IsPathDirectory(fullpath) && !libsys->CreateFolder(fullpath)) dvander@1203: { dvander@1203: break; dvander@1203: } dvander@1203: cur_ptr = next_ptr; dvander@1203: } while (cur_ptr); dvander@1203: } dvander@1203: dvander@1203: /* Build the FINAL path. */ dvander@1203: g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data/sqlite/%s.sq3", info->database); dvander@1203: dvander@1203: /* If we're requesting a persistent connection, see if something is already open */ dvander@1203: if (persistent) dvander@1203: { dvander@1203: /* See if anything in the cache matches */ dvander@1203: List::iterator iter; dvander@1203: for (iter = m_Cache.begin(); iter != m_Cache.end(); iter++) dvander@1203: { dvander@1203: if ((*iter).path.compare(fullpath) == 0) dvander@1203: { dvander@1203: (*iter).db->IncReferenceCount(); dvander@1203: m_pOpenLock->Unlock(); dvander@1203: return (*iter).db; dvander@1203: } dvander@1203: } dvander@1203: } dvander@1203: dvander@1203: /* Try to open a new connection */ dvander@1203: sqlite3 *sql; dvander@1203: int err = sqlite3_open(fullpath, &sql); dvander@1203: if (err != SQLITE_OK) dvander@1203: { dvander@1203: strncopy(error, sqlite3_errmsg(sql), maxlength); dvander@1203: sqlite3_close(sql); dvander@1203: m_pOpenLock->Unlock(); dvander@1203: return NULL; dvander@1203: } dvander@1203: dvander@1447: sqlite3_busy_handler(sql, busy_handler, NULL); dvander@1447: dvander@1203: SqDatabase *pdb = new SqDatabase(sql, persistent); dvander@1203: dvander@1203: if (persistent) dvander@1203: { dvander@1203: SqDbInfo pinfo; dvander@1203: pinfo.path = fullpath; dvander@1203: pinfo.db = pdb; dvander@1203: m_Cache.push_back(pinfo); dvander@1203: } dvander@1203: dvander@1203: m_pOpenLock->Unlock(); dvander@1203: dvander@1203: return pdb; dvander@1203: } dvander@1203: dvander@1203: void SqDriver::RemovePersistent(IDatabase *pdb) dvander@1203: { dvander@1203: List::iterator iter; dvander@1203: for (iter = m_Cache.begin(); iter != m_Cache.end(); iter++) dvander@1203: { dvander@1203: if ((*iter).db == pdb) dvander@1203: { dvander@1203: iter = m_Cache.erase(iter); dvander@1203: return; dvander@1203: } dvander@1203: } dvander@1203: }