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