From 4be487b9a9c0bb93587ed276c43fe3fba3e7c895 Mon Sep 17 00:00:00 2001 From: randomdan Date: Mon, 17 Oct 2011 19:29:44 +0000 Subject: Adds the JSON and CouchDB module --- project2/Jamfile.jam | 1 + project2/json/Jamfile.jam | 26 +++++ project2/json/conversion.cpp | 24 +++++ project2/json/conversion.h | 28 ++++++ project2/json/couchSession.cpp | 211 +++++++++++++++++++++++++++++++++++++++ project2/json/json.h | 43 ++++++++ project2/json/parse.cpp | 222 +++++++++++++++++++++++++++++++++++++++++ project2/json/pch.hpp | 12 +++ project2/json/serialize.cpp | 94 +++++++++++++++++ 9 files changed, 661 insertions(+) create mode 100644 project2/json/Jamfile.jam create mode 100644 project2/json/conversion.cpp create mode 100644 project2/json/conversion.h create mode 100644 project2/json/couchSession.cpp create mode 100644 project2/json/json.h create mode 100644 project2/json/parse.cpp create mode 100644 project2/json/pch.hpp create mode 100644 project2/json/serialize.cpp diff --git a/project2/Jamfile.jam b/project2/Jamfile.jam index e0797a3..65a0e49 100644 --- a/project2/Jamfile.jam +++ b/project2/Jamfile.jam @@ -13,6 +13,7 @@ alias p2parts : : : : mail//p2mail regex//p2regex xml//p2xml + json//p2json ; project diff --git a/project2/json/Jamfile.jam b/project2/json/Jamfile.jam new file mode 100644 index 0000000..92a6e28 --- /dev/null +++ b/project2/json/Jamfile.jam @@ -0,0 +1,26 @@ +alias libxmlpp : : : : + "`pkg-config --cflags libxml++-2.6`" + "`pkg-config --libs libxml++-2.6`" ; +lib boost_filesystem : : boost_filesystem ; +lib boost_date_time : : boost_date_time ; + +cpp-pch pch : pch.hpp : + ../../libmisc + libxmlpp + ../common//p2common + ; +lib p2json : + pch [ glob *.cpp ] + : + ../libmisc + libxmlpp + ../common//p2common + ../uuid//p2uuid + ../url//p2url + boost_filesystem + boost_date_time + : : + ../uuid//p2uuid + . + ; + diff --git a/project2/json/conversion.cpp b/project2/json/conversion.cpp new file mode 100644 index 0000000..d13a324 --- /dev/null +++ b/project2/json/conversion.cpp @@ -0,0 +1,24 @@ +#include +#include "conversion.h" +#include + +json::Value Project2ToJson::operator()(const boost::posix_time::ptime & i) const { + return boost::posix_time::to_iso_extended_string(i); +} +json::Value Project2ToJson::operator()(const Null & c) const { + return c; +} +json::Value Project2ToJson::operator()(const Glib::ustring & c) const { + return c; +} + +VariableType JsonToProject2::operator()(const json::Boolean &) const { + throw ConversionNotSupported(); +} +VariableType JsonToProject2::operator()(const json::Object &) const { + throw ConversionNotSupported(); +} +VariableType JsonToProject2::operator()(const json::Array &) const { + throw ConversionNotSupported(); +} + diff --git a/project2/json/conversion.h b/project2/json/conversion.h new file mode 100644 index 0000000..acd22fb --- /dev/null +++ b/project2/json/conversion.h @@ -0,0 +1,28 @@ +#include "variables.h" +#include "json.h" +#include + +class ConversionNotSupported { }; +class Project2ToJson : public boost::static_visitor { + public: + json::Value operator()(const boost::posix_time::ptime & i) const; + json::Value operator()(const Null & c) const; + json::Value operator()(const Glib::ustring & c) const; + template + json::Value operator()(const Numeric & i) const { + return boost::numeric_cast(i); + } +}; + +class JsonToProject2 : public boost::static_visitor { + public: + VariableType operator()(const json::Boolean &) const; + VariableType operator()(const json::Object &) const; + VariableType operator()(const json::Array &) const; + template + VariableType operator()(const Copyable & i) const { + return i; + } +}; + + diff --git a/project2/json/couchSession.cpp b/project2/json/couchSession.cpp new file mode 100644 index 0000000..d2eeca2 --- /dev/null +++ b/project2/json/couchSession.cpp @@ -0,0 +1,211 @@ +#include +#include "curlHelper.h" +#include "safeMapFind.h" +#include "exceptions.h" +#include "../libmisc/buffer.h" +#include "../libmisc/curlsup.h" +#include +#include +#include +#include +#include "json.h" +#include "safeMapFind.h" +#include "conversion.h" +#include + +class CouchSession : public Session { + public: + CouchSession(const UUID & sid) : Session(sid) { + } + CouchSession(const UUID & sid, const json::Object & o) : Session(sid), obj(o) { + } + virtual VariableType GetValue(const Glib::ustring & name) const { + return boost::apply_visitor(JsonToProject2(), *safeMapFind(obj, name)->second); + } + virtual Values GetValuesCopy() const { + Values vs; + BOOST_FOREACH(const json::Object::value_type & v, obj) { + vs.insert(Values::value_type(v.first, boost::apply_visitor(JsonToProject2(), *v.second))); + } + return vs; + } + virtual void SetValue(const Glib::ustring & name, const VariableType & v) { + obj[name] = json::ValuePtr(new json::Value(boost::apply_visitor(Project2ToJson(), v))); + } + virtual void ClearValue(const Glib::ustring & name) { + obj.erase(name); + } + virtual time_t ExpiryTime() const { + return boost::get(*safeMapFind(obj, ExpiryKey)->second); + } + virtual void ExpiryTime(time_t t) { + obj[ExpiryKey] = json::ValuePtr(new json::Value(json::Number(t))); + } + private: + json::Object obj; + friend class CouchSessionContainer; + friend class CustomCouchSessionLoader; + static const Glib::ustring ExpiryKey; +}; +const Glib::ustring CouchSession::ExpiryKey("project2:expires"); + +class CouchDBFailure : public std::exception { }; + +class CouchSessionContainer : public SessionContainer { + public: + CouchSessionContainer() { + } + virtual SessionPtr getSession(const UUID & uuid) const { + try { + SessionPtr s = new CouchSession(uuid, getSessionFromServer(uuid)); + if (s->ExpiryTime() > time(NULL)) { + return s; + } + } + catch (...) { + } + return new CouchSession(UUID::generate_random()); + } + + virtual void SaveSession(SessionPtr s) const { + CurlPtr c = new Curl(); + c->setopt(CURLOPT_UPLOAD, 1L); + c->setopt(CURLOPT_FAILONERROR, 1); + Glib::ustring out = json::serializeObject(boost::dynamic_pointer_cast(s)->obj); + c->setopt(CURLOPT_INFILESIZE_LARGE, (curl_off_t)out.size()); + unsigned int off = 0; + BOOST_FOREACH(const std::string & b, baseUrls) { + c->setopt(CURLOPT_URL, (b + s->ID.str()).c_str()); + try { + c->performSend(boost::bind(send, &out, &off, _1, _2)); + return; + } + catch (...) { + } + } + throw CouchDBFailure(); + } + + json::Object getSessionFromServer(const UUID & uuid) const { + CurlPtr c = new Curl(); + c->setopt(CURLOPT_FAILONERROR, 1); + Glib::ustring msg; + BOOST_FOREACH(const std::string & b, baseUrls) { + try { + c->setopt(CURLOPT_URL, (b + uuid.str()).c_str()); + c->performRead(boost::bind(append, &msg, _1, _2)); + json::Object o = json::parseObject(msg); + return o; + } + catch (...) { + } + } + throw CouchDBFailure(); + } + + static size_t send(Glib::ustring * buf, unsigned int * off, char * str, size_t l) { + size_t len = std::min(buf->size() - *off, l); + memcpy(str, buf->c_str() + *off, len); + return len; + } + + static size_t append(Glib::ustring * buf, const char * str, size_t l) { + buf->append(str, l); + return l; + } + + void setopt_s(CurlHandle::Ptr c, CURLoption o, const char * v) { + c->setopt(o, v); + } + + void setopt_l(CurlHandle::Ptr c, CURLoption o, int64_t v) { + c->setopt(o, (long)v); + } + + static std::vector baseUrls; +}; +std::vector CouchSessionContainer::baseUrls; + +namespace po = boost::program_options; +class CustomCouchSessionLoader : public SessionContainerLoaderImpl { + public: + CustomCouchSessionLoader() : + opts("Session CouchDB options") + { + opts.add_options() + ("session.couchdb.baseurl", po::value(&CouchSessionContainer::baseUrls)->composing(), + "Base URL to store sessions in") + ; + } + po::options_description * + options() + { + return &opts; + } + + void onPeriodic() { + deleteSessions(); + compactDB(); + } + + private: + static size_t discard(size_t l) { + return l; + } + + void compactDB() { + CurlPtr c = new Curl(); + c->setopt(CURLOPT_POST, 1); + c->appendHeader("Content-Type: application/json"); + BOOST_FOREACH(const std::string & b, CouchSessionContainer::baseUrls) { + c->setopt(CURLOPT_URL, (b + "_compact").c_str()); + c->performRead(boost::bind(discard, _2)); + } + } + void deleteSessions() { + // Create the server side search map + json::Object map; + Buffer mapBuf; + mapBuf.appendf("function(doc) { var exp = doc['%s']; if (exp < (new Date().getTime() / 1000)) { emit(exp, doc._rev); } }", + CouchSession::ExpiryKey.c_str()); + map["map"] = json::ValuePtr(new json::Value(mapBuf.str())); + Glib::ustring mapStr(json::serializeObject(map)); + // Create the CURL handle + CurlPtr c = new Curl(); + c->setopt(CURLOPT_FAILONERROR, 1); + c->appendHeader("Content-Type: application/json"); + c->setopt(CURLOPT_POST, 1); + c->setopt(CURLOPT_POSTFIELDS, mapStr.c_str()); + c->setopt(CURLOPT_POSTFIELDSIZE, mapStr.bytes()); + BOOST_FOREACH(const std::string & b, CouchSessionContainer::baseUrls) { + Glib::ustring msg; + try { + c->setopt(CURLOPT_URL, (b + "_temp_view").c_str()); + c->performRead(boost::bind(CouchSessionContainer::append, &msg, _1, _2)); + json::Object o = json::parseObject(msg); + BOOST_FOREACH(const json::Array::value_type & v, boost::get(*safeMapFind(o, "rows")->second)) { + json::Object rec = boost::get(*v); + UUID u = boost::get(*safeMapFind(rec, "id")->second).raw(); + Glib::ustring & rev = boost::get(*safeMapFind(rec, "value")->second); + deleteSession(u, rev); + } + return; + } + catch (...) { + } + } + } + + void deleteSession(const UUID & sid, const Glib::ustring & rev) const { + CurlPtr c = new Curl(); + c->setopt(CURLOPT_CUSTOMREQUEST, "DELETE"); + BOOST_FOREACH(const std::string & b, CouchSessionContainer::baseUrls) { + c->setopt(CURLOPT_URL, (b + sid.str() + "?rev=" + rev).c_str()); + c->performRead(boost::bind(discard, _2)); + return; + } + } + po::options_description opts; +}; +DECLARE_CUSTOM_COMPONENT_LOADER("couchsession", CouchSessionContainer, CustomCouchSessionLoader, SessionContainerLoader); + diff --git a/project2/json/json.h b/project2/json/json.h new file mode 100644 index 0000000..44ad9e4 --- /dev/null +++ b/project2/json/json.h @@ -0,0 +1,43 @@ +#ifndef JSON_H +#define JSON_H + +#include +#include +#include + +namespace json { + typedef Glib::ustring String; + typedef double Number; + typedef bool Boolean; + class Value; + typedef boost::shared_ptr ValuePtr; + typedef std::map Object; + typedef std::list Array; + typedef boost::variant VT; + class Value : public VT { + public: + template + Value(const X & x) : VT(x) { } + }; + + Object parseObject(Glib::ustring::const_iterator &); + Object parseObject(const Glib::ustring &); + String parseString(Glib::ustring::const_iterator & s); + Number parseNumber(Glib::ustring::const_iterator & s); + Boolean parseBoolean(Glib::ustring::const_iterator & s); + Null parseNull(Glib::ustring::const_iterator & s); + Value parseValue(Glib::ustring::const_iterator & s); + Array parseArray(Glib::ustring::const_iterator &); + + void serializeObject(const Object &, Glib::ustring & s); + void serializeValue(const Value &, Glib::ustring & s); + void serializeArray(const Array &, Glib::ustring & s); + void serializeString(const String &, Glib::ustring & s); + void serializeNumber(const Number &, Glib::ustring & s); + void serializeBoolean(const Boolean &, Glib::ustring & s); + void serializeNull(const Null &, Glib::ustring & s); + Glib::ustring serializeObject(const Object &); +} + +#endif + diff --git a/project2/json/parse.cpp b/project2/json/parse.cpp new file mode 100644 index 0000000..66735cb --- /dev/null +++ b/project2/json/parse.cpp @@ -0,0 +1,222 @@ +#include "json.h" +#include + +namespace json { + class ParseError { }; + String parseString(Glib::ustring::const_iterator & s) { + while (Glib::Unicode::isspace(*s)) s++; + if (*s++ != '"') throw ParseError(); + String str; + while (*s != '"') { + if (*s == '\\') { + switch (*s) { + case '"': + str += '"'; + break; + case '\\': + str += '\\'; + break; + case '/': + str += '/'; + break; + case 'b': + str += gunichar(8); + break; + case 'f': + str += gunichar(12); + break; + case 'n': + str += gunichar(10); + break; + case 'r': + str += gunichar(13); + break; + case 't': + str += gunichar(9); + break; + case 'u': + { + unsigned int c = 0; + for (int n = 0; n < 4; n += 1) { + c *= 16; + if (*s >= '0' && *s <= '9') { + c += (*s - '0'); + } + else if (*s >= 'a' && *s <= 'f') { + c += (*s - 'a'); + } + else if (*s >= 'A' && *s <= 'F') { + c += (*s - 'A'); + } + else { + throw ParseError(); + } + s++; + } + s--; + } + break; + default: + throw ParseError(); + } + } + else { + str += *s; + } + s++; + } + if (*s++ != '"') throw ParseError(); + return str; + } + Number parseNumber(Glib::ustring::const_iterator & s) { + while (Glib::Unicode::isspace(*s)) s++; + bool neg = false; + double v = 0; + if (*s == '-') { + neg = true; + s++; + } + bool dot = false, e = false; + double frac = 1; + unsigned int digits = 0; + while (true) { + if (Glib::Unicode::isdigit(*s)) { + if (dot) { + frac /= 10; + v += (frac * (*s - '0')); + } + else { + v *= 10; + v += (*s - '0'); + digits += 1; + } + } + else if (*s == '.') { + if (dot || e) throw ParseError(); + dot = true; + } + else if (*s == 'e' || *s == 'E') { + e = true; + s++; + bool eneg = false; + if (*s == '+') { + } + else if (*s == '-') { + eneg = true; + s++; + } + int ev = 0; + while (Glib::Unicode::isdigit(*s)) { + ev *= 10; + ev += (*s - '0'); + s++; + } + while (ev--) { + if (eneg) { + v /= 10; + } + else { + v *= 10; + } + } + break; + } + else { + break; + } + s++; + } + if (digits < 1) throw ParseError(); + return neg ? -v : v; + } + Value parseValue(Glib::ustring::const_iterator & s) { + while (Glib::Unicode::isspace(*s)) s++; + switch (*s) { + case '"': + return parseString(s); + case '{': + return parseObject(s); + case '[': + return parseArray(s); + case 'n': + return parseNull(s); + case 't': + case 'f': + return parseBoolean(s); + default: + return parseNumber(s); + } + } + Object parseObject(const Glib::ustring & s) { + Glib::ustring::const_iterator i = s.begin(); + Object o = parseObject(i); + if (i != s.end()) throw ParseError(); + return o; + } + Object parseObject(Glib::ustring::const_iterator & s) { + Object o; + while (Glib::Unicode::isspace(*s)) s++; + if (*s != '{') throw ParseError(); + do { + s++; + while (Glib::Unicode::isspace(*s)) s++; + if (*s == '}') return o; + String key = parseString(s); + while (Glib::Unicode::isspace(*s)) s++; + if (*s++ != ':') throw ParseError(); + if (!o.insert(Object::value_type(key, ValuePtr(new Value(parseValue(s))))).second) throw ParseError(); + while (Glib::Unicode::isspace(*s)) s++; + } while (*s == ','); + if (*s == '}') { + s++; + while (Glib::Unicode::isspace(*s)) s++; + return o; + } + throw ParseError(); + } + Array parseArray(Glib::ustring::const_iterator & s) { + Array a; + while (Glib::Unicode::isspace(*s)) s++; + if (*s != '[') throw ParseError(); + do { + s++; + while (Glib::Unicode::isspace(*s)) s++; + if (*s == ']') { + s++; + return a; + } + a.push_back(ValuePtr(new Value(parseValue(s)))); + while (Glib::Unicode::isspace(*s)) s++; + } while (*s == ','); + if (*s == ']') { + s++; + return a; + } + throw ParseError(); + } + Null parseNull(Glib::ustring::const_iterator & s) { + if (*s++ != 'n') throw ParseError(); + if (*s++ != 'u') throw ParseError(); + if (*s++ != 'l') throw ParseError(); + if (*s++ != 'l') throw ParseError(); + return Null(); + } + Boolean parseBoolean(Glib::ustring::const_iterator & s) { + if (*s == 't') { + s++; + if (*s++ != 'r') throw ParseError(); + if (*s++ != 'u') throw ParseError(); + if (*s++ != 'e') throw ParseError(); + return true; + } + else if (*s == 'f') { + s++; + if (*s++ != 'a') throw ParseError(); + if (*s++ != 'l') throw ParseError(); + if (*s++ != 's') throw ParseError(); + if (*s++ != 'e') throw ParseError(); + return false; + } + throw ParseError(); + } +} diff --git a/project2/json/pch.hpp b/project2/json/pch.hpp new file mode 100644 index 0000000..173770f --- /dev/null +++ b/project2/json/pch.hpp @@ -0,0 +1,12 @@ +#ifdef BOOST_BUILD_PCH_ENABLED +#ifndef JSON_PCH +#define JSON_PCH + +#include +#include +#include +#include + +#endif +#endif + diff --git a/project2/json/serialize.cpp b/project2/json/serialize.cpp new file mode 100644 index 0000000..f3245e9 --- /dev/null +++ b/project2/json/serialize.cpp @@ -0,0 +1,94 @@ +#include "json.h" +#include +#include +#include + +namespace json { + class JsonSerialize : public boost::static_visitor<> { + public: + JsonSerialize(Glib::ustring & out) : o(out) { + } + void operator()(const String & s) const { + serializeString(s, o); + } + void operator()(const Number & s) const { + serializeNumber(s, o); + } + void operator()(const Array & s) const { + serializeArray(s, o); + } + void operator()(const Object & s) const { + serializeObject(s, o); + } + void operator()(const Null & s) const { + serializeNull(s, o); + } + void operator()(const Boolean & s) const { + serializeBoolean(s, o); + } + private: + Glib::ustring & o; + }; + void serializeObject(const Object & o, Glib::ustring & s) { + s += '{'; + BOOST_FOREACH(const Object::value_type & v, o) { + if (&v != &*o.begin()) { + s += ','; + } + serializeString(v.first, s); + s += ':'; + serializeValue(*v.second, s); + } + s += '}'; + } + void serializeValue(const Value & v, Glib::ustring & s) { + boost::apply_visitor(JsonSerialize(s), v); + } + void serializeArray(const Array & a, Glib::ustring & s) { + s += '['; + BOOST_FOREACH(const Array::value_type & v, a) { + if (&v != &*a.begin()) { + s += ','; + } + serializeValue(v, s); + } + s += ']'; + } + void serializeString(const String & str, Glib::ustring & s) { + s += '"'; + BOOST_FOREACH(gunichar c, str) { + switch (c) { + case '\\': + s += "\\\\"; + break; + case '"': + s += "\\\""; + break; + default: + s += c; + break; + } + } + s += '"'; + } + void serializeNumber(const Number & n, Glib::ustring & s) { + s += boost::lexical_cast(n); + } + void serializeBoolean(const Boolean & b, Glib::ustring & s) { + if (b) { + s += "true"; + } + else { + s += "false"; + } + } + void serializeNull(const Null &, Glib::ustring & s) { + s += "null"; + } + Glib::ustring serializeObject(const Object & o) { + Glib::ustring out; + serializeObject(o, out); + return out; + } +} + -- cgit v1.2.3