diff --git a/CommonLibs/BitVector.cpp b/CommonLibs/BitVector.cpp
new file mode 100644
index 00000000..1d13dec9
--- /dev/null
+++ b/CommonLibs/BitVector.cpp
@@ -0,0 +1,603 @@
+/*
+* Copyright 2008, 2009 Free Software Foundation, 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 .
+
+*/
+
+
+
+
+#include "BitVector.h"
+#include
+#include
+
+using namespace std;
+
+
+/**
+ Apply a Galois polymonial to a binary seqeunce.
+ @param val The input sequence.
+ @param poly The polynomial.
+ @param order The order of the polynomial.
+ @return Single-bit result.
+*/
+unsigned applyPoly(uint64_t val, uint64_t poly, unsigned order)
+{
+ uint64_t prod = val & poly;
+ unsigned sum = prod;
+ for (unsigned i=1; i>i;
+ return sum & 0x01;
+}
+
+
+
+
+
+
+BitVector::BitVector(const char *valString)
+ :Vector(strlen(valString))
+{
+ uint32_t accum = 0;
+ for (size_t i=0; i=0; i--) {
+ accum = (accum<<1) | ((*dp--) & 0x01);
+ }
+ return accum;
+}
+
+
+
+
+uint64_t BitVector::readField(size_t& readIndex, unsigned length) const
+{
+ const uint64_t retVal = peekField(readIndex,length);
+ readIndex += length;
+ return retVal;
+}
+
+
+uint64_t BitVector::readFieldReversed(size_t& readIndex, unsigned length) const
+{
+ const uint64_t retVal = peekFieldReversed(readIndex,length);
+ readIndex += length;
+ return retVal;
+}
+
+
+
+
+
+void BitVector::fillField(size_t writeIndex, uint64_t value, unsigned length)
+{
+ char *dpBase = mStart + writeIndex;
+ char *dp = dpBase + length - 1;
+ assert(dp < mEnd);
+ while (dp>=dpBase) {
+ *dp-- = value & 0x01;
+ value >>= 1;
+ }
+}
+
+
+void BitVector::fillFieldReversed(size_t writeIndex, uint64_t value, unsigned length)
+{
+ char *dp = mStart + writeIndex;
+ char *dpEnd = dp + length - 1;
+ assert(dpEnd < mEnd);
+ while (dp<=dpEnd) {
+ *dp++ = value & 0x01;
+ value >>= 1;
+ }
+}
+
+
+
+
+void BitVector::writeField(size_t& writeIndex, uint64_t value, unsigned length)
+{
+ fillField(writeIndex,value,length);
+ writeIndex += length;
+}
+
+
+void BitVector::writeFieldReversed(size_t& writeIndex, uint64_t value, unsigned length)
+{
+ fillFieldReversed(writeIndex,value,length);
+ writeIndex += length;
+}
+
+
+void BitVector::invert()
+{
+ for (size_t i=0; i=8);
+
+ char tmp0 = mStart[0];
+ mStart[0] = mStart[7];
+ mStart[7] = tmp0;
+
+ char tmp1 = mStart[1];
+ mStart[1] = mStart[6];
+ mStart[6] = tmp1;
+
+ char tmp2 = mStart[2];
+ mStart[2] = mStart[5];
+ mStart[5] = tmp2;
+
+ char tmp3 = mStart[3];
+ mStart[3] = mStart[4];
+ mStart[4] = tmp3;
+}
+
+
+
+void BitVector::LSB8MSB()
+{
+ if (size()<8) return;
+ size_t size8 = 8*(size()/8);
+ size_t iTop = size8 - 8;
+ for (size_t i=0; i<=iTop; i+=8) segment(i,8).reverse8();
+}
+
+
+
+uint64_t BitVector::syndrome(Generator& gen) const
+{
+ gen.clear();
+ const char *dp = mStart;
+ while (dpiState) << 1; // input state for 0
+ const uint32_t iState1 = iState0 | 0x01; // input state for 1
+ const uint32_t oStateShifted = (sp->oState) << mIRate; // shifted output
+ const float cost = sp->cost;
+ sp++;
+ // 0 input extension
+ mCandidates[i].cost = cost;
+ mCandidates[i].oState = oStateShifted | mGeneratorTable[iState0 & mCMask];
+ mCandidates[i].iState = iState0;
+ // 1 input extension
+ mCandidates[i+1].cost = cost;
+ mCandidates[i+1].oState = oStateShifted | mGeneratorTable[iState1 & mCMask];
+ mCandidates[i+1].iState = iState1;
+ }
+}
+
+
+void ViterbiR2O4::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost)
+{
+ const float *cTab[2] = {matchCost,mismatchCost};
+ for (unsigned i=0; i>1)&0x01][0];
+ }
+}
+
+
+void ViterbiR2O4::pruneCandidates()
+{
+ const vCand* c1 = mCandidates; // 0-prefix
+ const vCand* c2 = mCandidates + mIStates; // 1-prefix
+ for (unsigned i=0; i=minCost) continue;
+ minCost = thisCost;
+ minIndex=i;
+ }
+ return mSurvivors[minIndex];
+}
+
+
+const ViterbiR2O4::vCand& ViterbiR2O4::step(uint32_t inSample, const float *probs, const float *iprobs)
+{
+ branchCandidates();
+ getSoftCostMetrics(inSample,probs,iprobs);
+ pruneCandidates();
+ return minCost();
+}
+
+
+uint64_t Parity::syndrome(const BitVector& receivedCodeword)
+{
+ return receivedCodeword.syndrome(*this);
+}
+
+
+void Parity::writeParityWord(const BitVector& data, BitVector& parityTarget, bool invert)
+{
+ uint64_t pWord = data.parity(*this);
+ if (invert) pWord = ~pWord;
+ parityTarget.fillField(0,pWord,size());
+}
+
+
+
+
+
+
+
+
+
+SoftVector::SoftVector(const BitVector& source)
+{
+ resize(source.size());
+ for (size_t i=0; i0.5F) newSig[i]=1;
+ else newSig[i] = 0;
+ }
+ return newSig;
+}
+
+
+
+void SoftVector::decode(ViterbiR2O4 &decoder, BitVector& target) const
+{
+ const size_t sz = size();
+ const unsigned deferral = decoder.deferral();
+ const size_t ctsz = sz + deferral*decoder.iRate();
+ assert(sz <= decoder.iRate()*target.size());
+
+ // Build a "history" array where each element contains the full history.
+ uint32_t history[ctsz];
+ {
+ BitVector bits = sliced();
+ uint32_t accum = 0;
+ for (size_t i=0; i0.5F) pVal = 1.0F-pVal;
+ float ipVal = 1.0F-pVal;
+ // This is a cheap approximation to an ideal cost function.
+ if (pVal<0.01F) pVal = 0.01;
+ if (ipVal<0.01F) ipVal = 0.01;
+ matchCostTable[i] = 0.25F/ipVal;
+ mismatchCostTable[i] = 0.25F/pVal;
+ }
+
+ // pad end of table with unknowns
+ for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01;
+ oCount++;
+ }
+ }
+}
+
+
+
+
+ostream& operator<<(ostream& os, const SoftVector& sv)
+{
+ for (size_t i=0; i0.75) os << "1";
+ else os << "-";
+ }
+ return os;
+}
+
+
+
+void BitVector::pack(unsigned char* targ) const
+{
+ // Assumes MSB-first packing.
+ unsigned bytes = size()/8;
+ for (unsigned i=0; i0) {
+ if (sscanf(src+digits, "%1x", &val) < 1) {
+ return false;
+ }
+ fillField(whole,val,rem);
+ }
+ return true;
+}
+
+// vim: ts=4 sw=4
diff --git a/CommonLibs/BitVector.h b/CommonLibs/BitVector.h
new file mode 100644
index 00000000..572e6b47
--- /dev/null
+++ b/CommonLibs/BitVector.h
@@ -0,0 +1,441 @@
+/*
+* Copyright 2008, 2009 Free Software Foundation, 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 .
+
+*/
+
+
+#ifndef FECVECTORS_H
+#define FECVECTORS_H
+
+#include "Vector.h"
+#include
+
+
+class BitVector;
+class SoftVector;
+
+
+
+/** Shift-register (LFSR) generator. */
+class Generator {
+
+ private:
+
+ uint64_t mCoeff; ///< polynomial coefficients. LSB is zero exponent.
+ uint64_t mState; ///< shift register state. LSB is most recent.
+ uint64_t mMask; ///< mask for reading state
+ unsigned mLen; ///< number of bits used in shift register
+ unsigned mLen_1; ///< mLen - 1
+
+ public:
+
+ Generator(uint64_t wCoeff, unsigned wLen)
+ :mCoeff(wCoeff),mState(0),
+ mMask((1ULL<>(mLen_1)) & 0x01;
+ mState = (mState<<1) ^ (inBit & 0x01);
+ if (fb) mState ^= mCoeff;
+ }
+
+ /**
+ Update the generator state by one cycle.
+ This is in the .h for inlining.
+ */
+ void encoderShift(unsigned inBit)
+ {
+ const unsigned fb = ((mState>>(mLen_1)) ^ inBit) & 0x01;
+ mState <<= 1;
+ if (fb) mState ^= mCoeff;
+ }
+
+
+};
+
+
+
+
+/** Parity (CRC-type) generator and checker based on a Generator. */
+class Parity : public Generator {
+
+ protected:
+
+ unsigned mCodewordSize;
+
+ public:
+
+ Parity(uint64_t wCoefficients, unsigned wParitySize, unsigned wCodewordSize)
+ :Generator(wCoefficients, wParitySize),
+ mCodewordSize(wCodewordSize)
+ { }
+
+ /** Compute the parity word and write it into the target segment. */
+ void writeParityWord(const BitVector& data, BitVector& parityWordTarget, bool invert=true);
+
+ /** Compute the syndrome of a received sequence. */
+ uint64_t syndrome(const BitVector& receivedCodeword);
+};
+
+
+
+
+/**
+ Class to represent convolutional coders/decoders of rate 1/2, memory length 4.
+ This is the "workhorse" coder for most GSM channels.
+*/
+class ViterbiR2O4 {
+
+ private:
+ /**name Lots of precomputed elements so the compiler can optimize like hell. */
+ //@{
+ /**@name Core values. */
+ //@{
+ static const unsigned mIRate = 2; ///< reciprocal of rate
+ static const unsigned mOrder = 4; ///< memory length of generators
+ //@}
+ /**@name Derived values. */
+ //@{
+ static const unsigned mIStates = 0x01 << mOrder; ///< number of states, number of survivors
+ static const uint32_t mSMask = mIStates-1; ///< survivor mask
+ static const uint32_t mCMask = (mSMask<<1) | 0x01; ///< candidate mask
+ static const uint32_t mOMask = (0x01< {
+
+
+ public:
+
+ /**@name Constructors. */
+ //@{
+
+ /**@name Casts of Vector constructors. */
+ //@{
+ BitVector(char* wData, char* wStart, char* wEnd)
+ :Vector(wData,wStart,wEnd)
+ { }
+ BitVector(size_t len=0):Vector(len) {}
+ BitVector(const Vector& source):Vector(source) {}
+ BitVector(Vector& source):Vector(source) {}
+ BitVector(const Vector& source1, const Vector source2):Vector(source1,source2) {}
+ //@}
+
+ /** Construct from a string of "0" and "1". */
+ BitVector(const char* valString);
+ //@}
+
+ /** Index a single bit. */
+ bool bit(size_t index) const
+ {
+ // We put this code in .h for fast inlining.
+ const char *dp = mStart+index;
+ assert(dp::segment(start,span)); }
+
+ BitVector head(size_t span) { return segment(0,span); }
+ const BitVector head(size_t span) const { return segment(0,span); }
+ BitVector tail(size_t start) { return segment(start,size()-start); }
+ const BitVector tail(size_t start) const { return segment(start,size()-start); }
+ //@}
+
+
+ void zero() { fill(0); }
+
+ /**@name FEC operations. */
+ //@{
+ /** Calculate the syndrome of the vector with the given Generator. */
+ uint64_t syndrome(Generator& gen) const;
+ /** Calculate the parity word for the vector with the given Generator. */
+ uint64_t parity(Generator& gen) const;
+ /** Encode the signal with the GSM rate 1/2 convolutional encoder. */
+ void encode(const ViterbiR2O4& encoder, BitVector& target);
+ //@}
+
+
+ /** Invert 0<->1. */
+ void invert();
+
+ /**@name Byte-wise operations. */
+ //@{
+ /** Reverse an 8-bit vector. */
+ void reverse8();
+ /** Reverse groups of 8 within the vector (byte reversal). */
+ void LSB8MSB();
+ //@}
+
+ /**@name Serialization and deserialization. */
+ //@{
+ uint64_t peekField(size_t readIndex, unsigned length) const;
+ uint64_t peekFieldReversed(size_t readIndex, unsigned length) const;
+ uint64_t readField(size_t& readIndex, unsigned length) const;
+ uint64_t readFieldReversed(size_t& readIndex, unsigned length) const;
+ void fillField(size_t writeIndex, uint64_t value, unsigned length);
+ void fillFieldReversed(size_t writeIndex, uint64_t value, unsigned length);
+ void writeField(size_t& writeIndex, uint64_t value, unsigned length);
+ void writeFieldReversed(size_t& writeIndex, uint64_t value, unsigned length);
+ //@}
+
+ /** Sum of bits. */
+ unsigned sum() const;
+
+ /** Reorder bits, dest[i] = this[map[i]]. */
+ void map(const unsigned *map, size_t mapSize, BitVector& dest) const;
+
+ /** Reorder bits, dest[map[i]] = this[i]. */
+ void unmap(const unsigned *map, size_t mapSize, BitVector& dest) const;
+
+ /** Pack into a char array. */
+ void pack(unsigned char*) const;
+
+ /** Unpack from a char array. */
+ void unpack(const unsigned char*);
+
+ /** Make a hexdump string. */
+ void hex(std::ostream&) const;
+
+ /** Unpack from a hexdump string.
+ * @returns true on success, false on error. */
+ bool unhex(const char*);
+
+};
+
+
+
+std::ostream& operator<<(std::ostream&, const BitVector&);
+
+
+
+
+
+
+/**
+ The SoftVector class is used to represent a soft-decision signal.
+ Values 0..1 represent probabilities that a bit is "true".
+ */
+class SoftVector: public Vector {
+
+ public:
+
+ /** Build a SoftVector of a given length. */
+ SoftVector(size_t wSize=0):Vector(wSize) {}
+
+ /** Construct a SoftVector from a C string of "0", "1", and "X". */
+ SoftVector(const char* valString);
+
+ /** Construct a SoftVector from a BitVector. */
+ SoftVector(const BitVector& source);
+
+ /**
+ Wrap a SoftVector around a block of floats.
+ The block will be delete[]ed upon desctuction.
+ */
+ SoftVector(float *wData, unsigned length)
+ :Vector(wData,length)
+ {}
+
+ SoftVector(float* wData, float* wStart, float* wEnd)
+ :Vector(wData,wStart,wEnd)
+ { }
+
+ /**
+ Casting from a Vector.
+ Note that this is NOT pass-by-reference.
+ */
+ SoftVector(Vector source)
+ :Vector(source)
+ {}
+
+
+ /**@name Casts and overrides of Vector operators. */
+ //@{
+ SoftVector segment(size_t start, size_t span)
+ {
+ float* wStart = mStart + start;
+ float* wEnd = wStart + span;
+ assert(wEnd<=mEnd);
+ return SoftVector(NULL,wStart,wEnd);
+ }
+
+ SoftVector alias()
+ { return segment(0,size()); }
+
+ const SoftVector segment(size_t start, size_t span) const
+ { return (SoftVector)(Vector::segment(start,span)); }
+
+ SoftVector head(size_t span) { return segment(0,span); }
+ const SoftVector head(size_t span) const { return segment(0,span); }
+ SoftVector tail(size_t start) { return segment(start,size()-start); }
+ const SoftVector tail(size_t start) const { return segment(start,size()-start); }
+ //@}
+
+ /** Decode soft symbols with the GSM rate-1/2 Viterbi decoder. */
+ void decode(ViterbiR2O4 &decoder, BitVector& target) const;
+
+ /** Fill with "unknown" values. */
+ void unknown() { fill(0.5F); }
+
+ /** Return a hard bit value from a given index by slicing. */
+ bool bit(size_t index) const
+ {
+ const float *dp = mStart+index;
+ assert(dp0.5F;
+ }
+
+ /** Slice the whole signal into bits. */
+ BitVector sliced() const;
+
+};
+
+
+
+std::ostream& operator<<(std::ostream&, const SoftVector&);
+
+
+
+
+
+
+#endif
+// vim: ts=4 sw=4
diff --git a/CommonLibs/BitVectorTest.cpp b/CommonLibs/BitVectorTest.cpp
new file mode 100644
index 00000000..5e487ad0
--- /dev/null
+++ b/CommonLibs/BitVectorTest.cpp
@@ -0,0 +1,88 @@
+/*
+* Copyright 2008 Free Software Foundation, 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 .
+
+*/
+
+
+
+
+#include "BitVector.h"
+#include
+#include
+
+using namespace std;
+
+
+int main(int argc, char *argv[])
+{
+ BitVector v1("0000111100111100101011110000");
+ cout << v1 << endl;
+ v1.LSB8MSB();
+ cout << v1 << endl;
+ ViterbiR2O4 vCoder;
+ BitVector v2(v1.size()*2);
+ v1.encode(vCoder,v2);
+ cout << v2 << endl;
+ SoftVector sv2(v2);
+ cout << sv2 << endl;
+ for (unsigned i=0; i.
+
+*/
+
+
+#include "Configuration.h"
+#include
+#include
+#include
+#include
+
+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 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 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> 32);
+ mHash = mHash*127 + cstr[i];
+ }
+}
+
+
+
+// vim: ts=4 sw=4
diff --git a/CommonLibs/Configuration.h b/CommonLibs/Configuration.h
new file mode 100644
index 00000000..c9c4cf3c
--- /dev/null
+++ b/CommonLibs/Configuration.h
@@ -0,0 +1,275 @@
+/*
+* Copyright 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 .
+
+*/
+
+
+#ifndef CONFIGURATION_H
+#define CONFIGURATION_H
+
+
+#include "sqlite3util.h"
+
+#include
+#include
+
+#include