Putting the actual OpenBTS P2.8 source code into the public SVN branch.


git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@2242 19bc5d8c-e614-43d4-8b26-e1612bc8e597
diff --git a/CommonLibs/Configuration.cpp b/CommonLibs/Configuration.cpp
new file mode 100644
index 0000000..3ad4f01
--- /dev/null
+++ b/CommonLibs/Configuration.cpp
@@ -0,0 +1,339 @@
+/*
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
+* Copyright 2010 Kestrel Signal Processing, Inc.
+*
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+	This program is free software: you can redistribute it and/or modify
+	it under the terms of the GNU Affero General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU Affero General Public License for more details.
+
+	You should have received a copy of the GNU Affero General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "Configuration.h"
+#include <fstream>
+#include <iostream>
+#include <string.h>
+#include <syslog.h>
+
+using namespace std;
+
+
+static const char* createConfigTable = {
+	"CREATE TABLE IF NOT EXISTS CONFIG ("
+		"KEYSTRING TEXT UNIQUE NOT NULL, "
+		"VALUESTRING TEXT, "
+		"STATIC INTEGER DEFAULT 0, "
+		"OPTIONAL INTEGER DEFAULT 0, "
+		"COMMENTS TEXT DEFAULT ''"
+	")"
+};
+
+
+ConfigurationTable::ConfigurationTable(const char* filename)
+{
+	// Connect to the database.
+	int rc = sqlite3_open(filename,&mDB);
+	if (rc) {
+		cerr << "Cannot open configuration database: " << sqlite3_errmsg(mDB);
+		sqlite3_close(mDB);
+		mDB = NULL;
+		return;
+	}
+	// Create the table, if needed.
+	if (!sqlite3_command(mDB,createConfigTable)) {
+		cerr << "Cannot create configuration table:" << sqlite3_errmsg(mDB);
+	}
+}
+
+
+
+bool ConfigurationTable::defines(const string& key)
+{
+	assert(mDB);
+	ScopedLock lock(mLock);
+
+	// Check the cache.
+	checkCacheAge();
+	ConfigurationMap::const_iterator where = mCache.find(key);
+	if (where!=mCache.end()) return where->second.defined();
+
+	// Check the database.
+	char *value = NULL;
+	sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"VALUESTRING",value);
+
+	// Cache the result.
+	if (value) {
+		mCache[key] = ConfigurationRecord(value);
+		free(value);
+		return true;
+	}
+	
+	mCache[key] = ConfigurationRecord(false);
+	return false;
+}
+
+
+const ConfigurationRecord& ConfigurationTable::lookup(const string& key)
+{
+	assert(mDB);
+	checkCacheAge();
+	// We assume the caller holds mLock.
+	// So it is OK to return a reference into the cache.
+
+	// Check the cache.
+	// This is cheap.
+	ConfigurationMap::const_iterator where = mCache.find(key);
+	if (where!=mCache.end()) {
+		if (where->second.defined()) return where->second;
+		// Unlock the mutex before throwing the exception.
+		mLock.unlock();
+		syslog(LOG_ALERT, "configuration key %s not found", key.c_str());
+		throw ConfigurationTableKeyNotFound(key);
+	}
+
+	// Check the database.
+	// This is more expensive.
+	char *value = NULL;
+	sqlite3_single_lookup(mDB,"CONFIG",
+			"KEYSTRING",key.c_str(),"VALUESTRING",value);
+
+	// Nothing defined?
+	if (!value) {
+		// Cache the failure.
+		mCache[key] = ConfigurationRecord(false);
+		// Unlock the mutex before throwing the exception.
+		mLock.unlock();
+		throw ConfigurationTableKeyNotFound(key);
+	}
+
+	// Cache the result.
+	mCache[key] = ConfigurationRecord(value);
+	free(value);
+
+	// Leave mLock locked.  The caller holds it still.
+	return mCache[key];
+}
+
+
+
+bool ConfigurationTable::isStatic(const string& key) const
+{
+	assert(mDB);
+	unsigned stat;
+	bool success = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"STATIC",stat);
+	if (success) return (bool)stat;
+	return false;
+}
+
+bool ConfigurationTable::isRequired(const string& key) const
+{
+	assert(mDB);
+	unsigned optional;
+	bool success = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"OPTIONAL",optional);
+	if (success) return !((bool)optional);
+	return false;
+}
+
+
+
+
+string ConfigurationTable::getStr(const string& key)
+{
+	// We need the lock because rec is a reference into the cache.
+	ScopedLock lock(mLock);
+	return lookup(key).value();
+}
+
+string ConfigurationTable::getStr(const string& key, const char* defaultValue)
+{
+	try {
+		return getStr(key);
+	} catch (ConfigurationTableKeyNotFound) {
+		set(key,defaultValue);
+		return string(defaultValue);
+	}
+}
+
+
+long ConfigurationTable::getNum(const string& key)
+{
+	// We need the lock because rec is a reference into the cache.
+	ScopedLock lock(mLock);
+	return lookup(key).number();
+}
+
+
+long ConfigurationTable::getNum(const string& key, long defaultValue)
+{
+	try {
+		return getNum(key);
+	} catch (ConfigurationTableKeyNotFound) {
+		set(key,defaultValue);
+		return defaultValue;
+	}
+}
+
+
+
+std::vector<unsigned> ConfigurationTable::getVector(const string& key)
+{
+	// Look up the string.
+	mLock.lock();
+	const ConfigurationRecord& rec = lookup(key);
+	char* line = strdup(rec.value().c_str());
+	mLock.unlock();
+	// Parse the string.
+	std::vector<unsigned> retVal;
+	char *lp=line;
+	while (lp) {
+		// Watch for multiple or trailing spaces.
+		while (*lp==' ') lp++;
+		if (*lp=='\0') break;
+		retVal.push_back(strtol(lp,NULL,0));
+		strsep(&lp," ");
+	}
+	free(line);
+	return retVal;
+}
+
+
+bool ConfigurationTable::unset(const string& key)
+{
+	assert(mDB);
+	if (!defines(key)) return true;
+	if (isRequired(key)) return false;
+
+	ScopedLock lock(mLock);
+	// Clear the cache entry and the database.
+	ConfigurationMap::iterator where = mCache.find(key);
+	if (where!=mCache.end()) mCache.erase(where);
+	// Don't delete it; just set VALUESTRING to NULL.
+	string cmd = "UPDATE CONFIG SET VALUESTRING=NULL WHERE KEYSTRING=='"+key+"'";
+	return sqlite3_command(mDB,cmd.c_str());
+}
+
+
+void ConfigurationTable::find(const string& pat, ostream& os) const
+{
+	// Prepare the statement.
+	string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG WHERE KEYSTRING LIKE \"%" + pat + "%\"";
+	sqlite3_stmt *stmt;
+	if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return;
+	// Read the result.
+	int src = sqlite3_run_query(mDB,stmt);
+	while (src==SQLITE_ROW) {
+		const char* value = (const char*)sqlite3_column_text(stmt,1);
+		os << sqlite3_column_text(stmt,0) << " ";
+		if (value) os << value << endl;
+		else os << "(null)" << endl;
+		src = sqlite3_run_query(mDB,stmt);
+	}
+	sqlite3_finalize(stmt);
+}
+
+
+bool ConfigurationTable::set(const string& key, const string& value)
+{
+	assert(mDB);
+	ScopedLock lock(mLock);
+	// Is it there already?
+	char * oldValue = NULL;
+	bool exists = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"VALUESTRING",oldValue);
+	// Update or insert as appropriate.
+	string cmd;
+	if (exists) cmd = "UPDATE CONFIG SET VALUESTRING=\""+value+"\" WHERE KEYSTRING==\""+key+"\"";
+	else cmd = "INSERT INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",\"" + value + "\",1)";
+	bool success = sqlite3_command(mDB,cmd.c_str());
+	// Cache the result.
+	if (success) mCache[key] = ConfigurationRecord(value);
+	return success;
+}
+
+bool ConfigurationTable::set(const string& key, long value)
+{
+	char buffer[30];
+	sprintf(buffer,"%ld",value);
+	return set(key,buffer);
+}
+
+
+bool ConfigurationTable::set(const string& key)
+{
+	assert(mDB);
+	ScopedLock lock(mLock);
+	string cmd = "INSERT INTO CONFIG (KEYSTRING) VALUES (\"" + key + "\")";
+	bool success = sqlite3_command(mDB,cmd.c_str());
+	if (success) mCache[key] = ConfigurationRecord(true);
+	return success;
+}
+
+
+void ConfigurationTable::checkCacheAge()
+{
+	// mLock is set by caller 
+	static time_t timeOfLastPurge = 0;
+	time_t now = time(NULL);
+	// purge every 3 seconds
+	// purge period cannot be configuration parameter
+	if (now - timeOfLastPurge < 3) return;
+	timeOfLastPurge = now;
+	// this is purge() without the lock
+	ConfigurationMap::iterator mp = mCache.begin();
+	while (mp != mCache.end()) {
+		ConfigurationMap::iterator prev = mp;
+		mp++;
+		mCache.erase(prev);
+	}
+}
+
+
+void ConfigurationTable::purge()
+{
+	ScopedLock lock(mLock);
+	ConfigurationMap::iterator mp = mCache.begin();
+	while (mp != mCache.end()) {
+		ConfigurationMap::iterator prev = mp;
+		mp++;
+		mCache.erase(prev);
+	}
+}
+
+
+void ConfigurationTable::setUpdateHook(void(*func)(void *,int ,char const *,char const *,sqlite3_int64))
+{
+	assert(mDB);
+	sqlite3_update_hook(mDB,func,NULL);
+}
+
+
+
+void HashString::computeHash()
+{
+	// FIXME -- Someone needs to review this hash function.
+	const char* cstr = c_str();
+	mHash = 0;
+	for (unsigned i=0; i<size(); i++) {
+		mHash = mHash ^ (mHash >> 32);
+		mHash = mHash*127 + cstr[i];
+	}
+}
+
+
+
+// vim: ts=4 sw=4