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: }