extensions/sqlite/driver/SqDriver.cpp
author Scott Ehlert <ds@alliedmods.net>
Sun Mar 30 07:00:22 2008 +0000 (2008-03-30)
changeset 1952 b7aa2b626807
child 2289 44045ead61cd
permissions -rw-r--r--
Spring Cleaning, Part Ichi (1)
Various minor things done to project files
Updated sample extension project file and updated makefile to the new unified version (more changes likely on the way)
Updated regex project file and makefile
ds@1952
     1
/**
ds@1952
     2
 * vim: set ts=4 :
ds@1952
     3
 * =============================================================================
ds@1952
     4
 * SourceMod SQLite Extension
ds@1952
     5
 * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
ds@1952
     6
 * =============================================================================
ds@1952
     7
 *
ds@1952
     8
 * This program is free software; you can redistribute it and/or modify it under
ds@1952
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
ds@1952
    10
 * Free Software Foundation.
ds@1952
    11
 * 
ds@1952
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
ds@1952
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
ds@1952
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
ds@1952
    15
 * details.
ds@1952
    16
 *
ds@1952
    17
 * You should have received a copy of the GNU General Public License along with
ds@1952
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
ds@1952
    19
 *
ds@1952
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
ds@1952
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
ds@1952
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
ds@1952
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
ds@1952
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
ds@1952
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
ds@1952
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
ds@1952
    27
 * or <http://www.sourcemod.net/license.php>.
ds@1952
    28
 *
ds@1952
    29
 * Version: $Id$
ds@1952
    30
 */
ds@1952
    31
ds@1952
    32
#include <sm_platform.h>
ds@1952
    33
#include "extension.h"
ds@1952
    34
#include "SqDriver.h"
ds@1952
    35
#include "SqDatabase.h"
ds@1952
    36
ds@1952
    37
SqDriver g_SqDriver;
ds@1952
    38
ds@1952
    39
unsigned int strncopy(char *dest, const char *src, size_t count)
ds@1952
    40
{
ds@1952
    41
	if (!count)
ds@1952
    42
	{
ds@1952
    43
		return 0;
ds@1952
    44
	}
ds@1952
    45
ds@1952
    46
	char *start = dest;
ds@1952
    47
	while ((*src) && (--count))
ds@1952
    48
	{
ds@1952
    49
		*dest++ = *src++;
ds@1952
    50
	}
ds@1952
    51
	*dest = '\0';
ds@1952
    52
ds@1952
    53
	return (dest - start);
ds@1952
    54
}
ds@1952
    55
ds@1952
    56
int busy_handler(void *unused1, int unused2)
ds@1952
    57
{
ds@1952
    58
#if defined PLATFORM_WINDOWS
ds@1952
    59
	Sleep(100);
ds@1952
    60
#elif defined PLATFORM_LINUX
ds@1952
    61
	usleep(100000);
ds@1952
    62
#endif
ds@1952
    63
	return 1;
ds@1952
    64
}
ds@1952
    65
ds@1952
    66
SqDriver::SqDriver()
ds@1952
    67
{
ds@1952
    68
	m_Handle = BAD_HANDLE;
ds@1952
    69
	m_pOpenLock = NULL;
ds@1952
    70
	m_bThreadSafe = false;
ds@1952
    71
}
ds@1952
    72
ds@1952
    73
void SqDriver::Initialize()
ds@1952
    74
{
ds@1952
    75
	m_pOpenLock = threader->MakeMutex();
ds@1952
    76
ds@1952
    77
	InitializeThreadSafety();
ds@1952
    78
}
ds@1952
    79
ds@1952
    80
void SqDriver::Shutdown()
ds@1952
    81
{
ds@1952
    82
	if (m_pOpenLock)
ds@1952
    83
	{
ds@1952
    84
		m_pOpenLock->DestroyThis();
ds@1952
    85
	}
ds@1952
    86
ds@1952
    87
	if (m_bThreadSafe)
ds@1952
    88
	{
ds@1952
    89
		sqlite3_enable_shared_cache(0);
ds@1952
    90
	}
ds@1952
    91
}
ds@1952
    92
ds@1952
    93
bool SqDriver::IsThreadSafe()
ds@1952
    94
{
ds@1952
    95
	return true;
ds@1952
    96
}
ds@1952
    97
ds@1952
    98
bool SqDriver::InitializeThreadSafety()
ds@1952
    99
{
ds@1952
   100
	if (m_bThreadSafe)
ds@1952
   101
	{
ds@1952
   102
		return true;
ds@1952
   103
	}
ds@1952
   104
ds@1952
   105
	if (sqlite3_threadsafe() == 0)
ds@1952
   106
	{
ds@1952
   107
		return false;
ds@1952
   108
	}
ds@1952
   109
ds@1952
   110
	if (sqlite3_enable_shared_cache(1) != SQLITE_OK)
ds@1952
   111
	{
ds@1952
   112
		return false;
ds@1952
   113
	}
ds@1952
   114
ds@1952
   115
	m_bThreadSafe = true;
ds@1952
   116
ds@1952
   117
	return true;
ds@1952
   118
}
ds@1952
   119
ds@1952
   120
void SqDriver::ShutdownThreadSafety()
ds@1952
   121
{
ds@1952
   122
	return;
ds@1952
   123
}
ds@1952
   124
ds@1952
   125
IdentityToken_t *SqDriver::GetIdentity()
ds@1952
   126
{
ds@1952
   127
	return myself->GetIdentity();
ds@1952
   128
}
ds@1952
   129
ds@1952
   130
const char *SqDriver::GetProductName()
ds@1952
   131
{
ds@1952
   132
	return "SQLite";
ds@1952
   133
}
ds@1952
   134
ds@1952
   135
const char *SqDriver::GetIdentifier()
ds@1952
   136
{
ds@1952
   137
	return "sqlite";
ds@1952
   138
}
ds@1952
   139
ds@1952
   140
Handle_t SqDriver::GetHandle()
ds@1952
   141
{
ds@1952
   142
	if (m_Handle == BAD_HANDLE)
ds@1952
   143
	{
ds@1952
   144
		m_Handle = dbi->CreateHandle(DBHandle_Driver, this, myself->GetIdentity());
ds@1952
   145
	}
ds@1952
   146
ds@1952
   147
	return m_Handle;
ds@1952
   148
}
ds@1952
   149
ds@1952
   150
inline bool IsPathSepChar(char c)
ds@1952
   151
{
ds@1952
   152
#if defined PLATFORM_WINDOWS
ds@1952
   153
	return (c == '\\' || c == '/');
ds@1952
   154
#elif defined PLATFORM_LINUX
ds@1952
   155
	return (c == '/');
ds@1952
   156
#endif
ds@1952
   157
}
ds@1952
   158
ds@1952
   159
IDatabase *SqDriver::Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength)
ds@1952
   160
{
ds@1952
   161
	/* We wrap most of the open process in a mutex just to be safe */
ds@1952
   162
	m_pOpenLock->Lock();
ds@1952
   163
ds@1952
   164
	/* Format our path */
ds@1952
   165
	char path[PLATFORM_MAX_PATH];
ds@1952
   166
	size_t len = libsys->PathFormat(path, sizeof(path), "sqlite/%s", info->database);
ds@1952
   167
ds@1952
   168
	/* Chop any filename off */
ds@1952
   169
	for (size_t i = len-1;
ds@1952
   170
		 i >= 0 && i <= len-1;
ds@1952
   171
		 i--)
ds@1952
   172
	{
ds@1952
   173
		if (IsPathSepChar(path[i]))
ds@1952
   174
		{
ds@1952
   175
			path[i] = '\0';
ds@1952
   176
			break;
ds@1952
   177
		}
ds@1952
   178
	}
ds@1952
   179
ds@1952
   180
	/* Test the full path */
ds@1952
   181
	char fullpath[PLATFORM_MAX_PATH];
ds@1952
   182
	g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data/%s", path);
ds@1952
   183
	if (!libsys->IsPathDirectory(fullpath))
ds@1952
   184
	{
ds@1952
   185
		/* Make sure the data folder exists */
ds@1952
   186
		len = g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data");
ds@1952
   187
		if (!libsys->IsPathDirectory(fullpath))
ds@1952
   188
		{
ds@1952
   189
			if (!libsys->CreateFolder(fullpath))
ds@1952
   190
			{
ds@1952
   191
				strncopy(error, "Could not create or open \"data\" folder\"", maxlength);
ds@1952
   192
				m_pOpenLock->Unlock();
ds@1952
   193
				return NULL;
ds@1952
   194
			}
ds@1952
   195
		}
ds@1952
   196
ds@1952
   197
		/* The data folder exists - create each subdir as needed! */
ds@1952
   198
		char *cur_ptr = path;
ds@1952
   199
ds@1952
   200
		do
ds@1952
   201
		{
ds@1952
   202
			/* Find the next suitable path */
ds@1952
   203
			char *next_ptr = cur_ptr;
ds@1952
   204
			while (*next_ptr != '\0')
ds@1952
   205
			{
ds@1952
   206
				if (IsPathSepChar(*next_ptr))
ds@1952
   207
				{
ds@1952
   208
					*next_ptr = '\0';
ds@1952
   209
					next_ptr++;
ds@1952
   210
					break;
ds@1952
   211
				}
ds@1952
   212
				next_ptr++;
ds@1952
   213
			}
ds@1952
   214
			if (*next_ptr == '\0')
ds@1952
   215
			{
ds@1952
   216
				next_ptr = NULL;
ds@1952
   217
			}
ds@1952
   218
			len += libsys->PathFormat(&fullpath[len], sizeof(fullpath)-len, "/%s", cur_ptr);
ds@1952
   219
			if (!libsys->IsPathDirectory(fullpath) && !libsys->CreateFolder(fullpath))
ds@1952
   220
			{
ds@1952
   221
				break;
ds@1952
   222
			}
ds@1952
   223
			cur_ptr = next_ptr;
ds@1952
   224
		} while (cur_ptr);
ds@1952
   225
	}
ds@1952
   226
ds@1952
   227
	/* Build the FINAL path. */
ds@1952
   228
	g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data/sqlite/%s.sq3", info->database);
ds@1952
   229
ds@1952
   230
	/* If we're requesting a persistent connection, see if something is already open */
ds@1952
   231
	if (persistent)
ds@1952
   232
	{
ds@1952
   233
		/* See if anything in the cache matches */
ds@1952
   234
		List<SqDbInfo>::iterator iter;
ds@1952
   235
		for (iter = m_Cache.begin(); iter != m_Cache.end(); iter++)
ds@1952
   236
		{
ds@1952
   237
			if ((*iter).path.compare(fullpath) == 0)
ds@1952
   238
			{
ds@1952
   239
				(*iter).db->IncReferenceCount();
ds@1952
   240
				m_pOpenLock->Unlock();
ds@1952
   241
				return (*iter).db;
ds@1952
   242
			}
ds@1952
   243
		}
ds@1952
   244
	}
ds@1952
   245
ds@1952
   246
	/* Try to open a new connection */
ds@1952
   247
	sqlite3 *sql;
ds@1952
   248
	int err = sqlite3_open(fullpath, &sql);
ds@1952
   249
	if (err != SQLITE_OK)
ds@1952
   250
	{
ds@1952
   251
		strncopy(error, sqlite3_errmsg(sql), maxlength);
ds@1952
   252
		sqlite3_close(sql);
ds@1952
   253
		m_pOpenLock->Unlock();
ds@1952
   254
		return NULL;
ds@1952
   255
	}
ds@1952
   256
ds@1952
   257
	sqlite3_busy_handler(sql, busy_handler, NULL);
ds@1952
   258
ds@1952
   259
	SqDatabase *pdb = new SqDatabase(sql, persistent);
ds@1952
   260
ds@1952
   261
	if (persistent)
ds@1952
   262
	{
ds@1952
   263
		SqDbInfo pinfo;
ds@1952
   264
		pinfo.path = fullpath;
ds@1952
   265
		pinfo.db = pdb;
ds@1952
   266
		m_Cache.push_back(pinfo);
ds@1952
   267
	}
ds@1952
   268
ds@1952
   269
	m_pOpenLock->Unlock();
ds@1952
   270
ds@1952
   271
	return pdb;
ds@1952
   272
}
ds@1952
   273
ds@1952
   274
void SqDriver::RemovePersistent(IDatabase *pdb)
ds@1952
   275
{
ds@1952
   276
	List<SqDbInfo>::iterator iter;
ds@1952
   277
	for (iter = m_Cache.begin(); iter != m_Cache.end(); iter++)
ds@1952
   278
	{
ds@1952
   279
		if ((*iter).db == pdb)
ds@1952
   280
		{
ds@1952
   281
			iter = m_Cache.erase(iter);
ds@1952
   282
			return;
ds@1952
   283
		}
ds@1952
   284
	}
ds@1952
   285
}