Remove unused dependency
This commit is contained in:
parent
006e106ae9
commit
767aac274b
26 changed files with 0 additions and 11043 deletions
62
external/glim/ChangeLog.darcs.txt
vendored
62
external/glim/ChangeLog.darcs.txt
vendored
|
@ -1,62 +0,0 @@
|
||||||
Wed Dec 6 23:50:10 Russian Standard Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Fixes discovered under FreeBSD.
|
|
||||||
|
|
||||||
Wed Dec 6 04:43:04 Russian Standard Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Ported to Gentoo gcc 4.1.1
|
|
||||||
|
|
||||||
Fri Nov 24 14:17:08 Russian Standard Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Documentation improvements for the release.
|
|
||||||
|
|
||||||
Fri Nov 24 13:13:50 Russian Standard Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* A few fixes.
|
|
||||||
|
|
||||||
Fri Nov 24 13:13:20 Russian Standard Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Do not remove the "html" directory when cleaning.
|
|
||||||
|
|
||||||
Wed Nov 22 15:30:56 Russian Standard Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Added a compatibility #pair method to bufstr_t.
|
|
||||||
|
|
||||||
Tue Nov 21 21:54:40 Russian Standard Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Implemented the sugared way to querying.
|
|
||||||
|
|
||||||
Mon Oct 9 23:19:11 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Fixed incorrect statement = NULL in #step. Implemented parameter bindings.
|
|
||||||
|
|
||||||
Mon Oct 9 20:09:17 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* SqliteQuery and SqliteParQuery classes (no binding support as of yet).
|
|
||||||
|
|
||||||
Fri Oct 6 12:11:43 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* typo
|
|
||||||
|
|
||||||
Thu Oct 5 23:26:21 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Basic mutex operations in SqliteSession.
|
|
||||||
|
|
||||||
Sun Oct 1 23:19:36 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Compatibility with std::string.
|
|
||||||
|
|
||||||
Fri Sep 29 11:42:09 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Invented SqliteSession.
|
|
||||||
|
|
||||||
Fri Sep 29 01:23:31 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* SQLite wrapper: initial documentation; opening, closing.
|
|
||||||
|
|
||||||
Thu Sep 28 23:15:37 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Apache version 2 license.
|
|
||||||
|
|
||||||
Thu Sep 28 23:12:41 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Multiple source files for tests.
|
|
||||||
|
|
||||||
Thu Sep 28 01:05:21 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Append from another bufstr_t and from a pair.
|
|
||||||
|
|
||||||
Thu Sep 28 01:04:46 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Macro to construct pair from C character array.
|
|
||||||
|
|
||||||
Tue Sep 26 23:23:40 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* char const* instead of const char*
|
|
||||||
|
|
||||||
Tue Sep 26 20:56:07 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Everything seems to work. The library is now headers-only.
|
|
||||||
|
|
||||||
Mon Sep 25 22:52:34 Russian Daylight Time 2006 ArtemGr <artem@bizlink.ru>
|
|
||||||
* Initial revision, containing bufstr_t; compiles, but does not work.
|
|
13
external/glim/LICENSE
vendored
13
external/glim/LICENSE
vendored
|
@ -1,13 +0,0 @@
|
||||||
Copyright 2006-2012 Kozarezov Artem Aleksandrovich <artemciy@gmail.com>
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
51
external/glim/NsecTimer.hpp
vendored
51
external/glim/NsecTimer.hpp
vendored
|
@ -1,51 +0,0 @@
|
||||||
#ifndef _NSEC_TIMER_H
|
|
||||||
#define _NSEC_TIMER_H
|
|
||||||
|
|
||||||
#include <time.h> // clock_gettime, CLOCK_MONOTONIC
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
//! Safe nanoseconds timer.
|
|
||||||
struct NsecTimer {
|
|
||||||
timespec start;
|
|
||||||
NsecTimer () {restart();}
|
|
||||||
//! Nanoseconds since the creation or restart of the timer.
|
|
||||||
int64_t operator()() const {
|
|
||||||
timespec nsecStop; clock_gettime (CLOCK_MONOTONIC, &nsecStop);
|
|
||||||
return (int64_t) (nsecStop.tv_sec - start.tv_sec) * 1000000000LL + (int64_t) (nsecStop.tv_nsec - start.tv_nsec);
|
|
||||||
}
|
|
||||||
/** Seconds since the creation or restart of the timer. */
|
|
||||||
double sec() const {
|
|
||||||
timespec nsecStop; clock_gettime (CLOCK_MONOTONIC, &nsecStop);
|
|
||||||
double seconds = nsecStop.tv_sec - start.tv_sec;
|
|
||||||
seconds += (double)(nsecStop.tv_nsec - start.tv_nsec) / 1000000000.0;
|
|
||||||
return seconds;
|
|
||||||
}
|
|
||||||
//! Seconds since the creation or restart of the timer.
|
|
||||||
std::string seconds (int precision = 9) const {
|
|
||||||
// The trick is to avoid the scientific notation by printing integers.
|
|
||||||
double sec = this->sec();
|
|
||||||
std::ostringstream buf;
|
|
||||||
int isec = (int) sec;
|
|
||||||
buf << isec;
|
|
||||||
|
|
||||||
sec -= isec;
|
|
||||||
for (int pc = precision; pc; --pc) sec *= 10.0;
|
|
||||||
int ifrac = (int) sec;
|
|
||||||
if (ifrac > 0) {
|
|
||||||
buf << '.';
|
|
||||||
buf.fill ('0'); buf.width (precision);
|
|
||||||
buf << ifrac;
|
|
||||||
}
|
|
||||||
return buf.str();
|
|
||||||
}
|
|
||||||
void restart() {clock_gettime (CLOCK_MONOTONIC, &start);}
|
|
||||||
int64_t getAndRestart() {int64_t tmp = operator()(); restart(); return tmp;}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // _NSEC_TIMER_H
|
|
236
external/glim/SerializablePool.hpp
vendored
236
external/glim/SerializablePool.hpp
vendored
|
@ -1,236 +0,0 @@
|
||||||
#ifndef _GLIM_SERIALIZABLEPOOL_HPP_INCLUDED
|
|
||||||
#define _GLIM_SERIALIZABLEPOOL_HPP_INCLUDED
|
|
||||||
|
|
||||||
#include "gstring.hpp"
|
|
||||||
#ifndef _SERIALIZABLEPOOL_NOLDB
|
|
||||||
# include "ldb.hpp" // Reuse `ldbSerialize` and `ldbDeserialize` in the `with` method.
|
|
||||||
#endif
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
namespace SerializablePoolHelpers {
|
|
||||||
struct Impl {
|
|
||||||
/**
|
|
||||||
* Pool format: \code
|
|
||||||
* uint32_t valuesStart; // Network byte order.
|
|
||||||
* uint32_t value0Offset, value1Offset, ... valueNOffset; // Network byte order.
|
|
||||||
* char value0[]; char zero; char value1[]; char zero; ... char valueN[]; char zero;
|
|
||||||
* \endcode
|
|
||||||
*/
|
|
||||||
gstring _pool;
|
|
||||||
std::vector<gstring> _changes;
|
|
||||||
std::vector<bool> _changed;
|
|
||||||
bool _readOnly = false;
|
|
||||||
Impl() = default;
|
|
||||||
Impl (const gstring& poolBytes): _pool (poolBytes), _readOnly (false) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Can be used to avoid a deep copy of the pool and change vectors (pImpl idiom).
|
|
||||||
/// Example: \code glim::SerializablePool pool; \endcode
|
|
||||||
struct SharedPtr {
|
|
||||||
std::shared_ptr<Impl> _impl;
|
|
||||||
SharedPtr() = default;
|
|
||||||
SharedPtr (const gstring& poolBytes): _impl (std::make_shared<Impl> (poolBytes)) {}
|
|
||||||
Impl* get() const {return _impl.get();}
|
|
||||||
Impl* operator->() const {return _impl.get();}
|
|
||||||
Impl& operator*() const {return *_impl;}
|
|
||||||
Impl* instance() {if (!_impl) _impl = std::make_shared<Impl>(); return _impl.get();}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Can be used instead of SharedPtr to avoid the shared_ptr indirection when the SerializablePoolTpl isn't going to be copied.
|
|
||||||
/// Example: \code glim::InlineSerializablePool temporaryPool (bytes); \endcode
|
|
||||||
struct InlinePtr {
|
|
||||||
Impl _impl;
|
|
||||||
InlinePtr() = default;
|
|
||||||
InlinePtr (const gstring& poolBytes): _impl (poolBytes) {}
|
|
||||||
Impl* get() const {return const_cast<Impl*> (&_impl);}
|
|
||||||
Impl* operator->() const {return const_cast<Impl*> (&_impl);}
|
|
||||||
Impl& operator*() const {return const_cast<Impl&> (_impl);}
|
|
||||||
Impl* instance() {return &_impl;}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Serialization with lazy parsing: fields are accessed without "unpacking" the byte array.
|
|
||||||
* Changes are stored separately, allowing the user to know exactly what fields has been changed and compare the old values to the new ones. */
|
|
||||||
template <typename PI>
|
|
||||||
class SerializablePoolTpl {
|
|
||||||
protected:
|
|
||||||
using Impl = SerializablePoolHelpers::Impl;
|
|
||||||
PI _impl;
|
|
||||||
/** @param ref Return a zero-copy view. The view should not be used outside of the pool buffer's lifetime. */
|
|
||||||
static gstring original (const gstring& pool, uint32_t num, bool ref = false) {
|
|
||||||
uint32_t poolLength = pool.length(); if (poolLength < 4) return gstring();
|
|
||||||
uint32_t valuesStart = ntohl (*(uint32_t*) pool.data());
|
|
||||||
assert (valuesStart <= poolLength);
|
|
||||||
uint32_t valueOffsetOffset = 4 + num * 4;
|
|
||||||
if ((int) valuesStart - (int) valueOffsetOffset < 4) return gstring(); // num > size
|
|
||||||
uint32_t valueOffset = ntohl (*(uint32_t*) (pool.data() + valueOffsetOffset));
|
|
||||||
valueOffsetOffset += 4;
|
|
||||||
uint32_t nextValueOffset = ((int) valuesStart - (int) valueOffsetOffset < 4)
|
|
||||||
? poolLength
|
|
||||||
: ntohl (*(uint32_t*) (pool.data() + valueOffsetOffset));
|
|
||||||
return gstring (0, (void*) (pool.data() + valueOffset), false, nextValueOffset - 1 - valueOffset, ref);
|
|
||||||
}
|
|
||||||
/** How many elements are in the pool. */
|
|
||||||
static uint32_t poolSize (const gstring& pool) {
|
|
||||||
if (pool.length() < 4) return 0;
|
|
||||||
uint32_t valuesStart = ntohl (*(uint32_t*) pool.data());
|
|
||||||
return (valuesStart - 4) / 4;
|
|
||||||
}
|
|
||||||
void toBytes (gstring& newPool, uint32_t size, const gstring* oldPool) const {
|
|
||||||
newPool.clear();
|
|
||||||
const Impl* impl = _impl.get();
|
|
||||||
const std::vector<bool>& changed = impl->_changed;
|
|
||||||
const std::vector<gstring>& changes = impl->_changes;
|
|
||||||
if (changed.empty()) return;
|
|
||||||
uint32_t valuesStart = 4 + size * 4;
|
|
||||||
uint32_t networkOrder = 0;
|
|
||||||
newPool.append ((char*) &(networkOrder = htonl (valuesStart)), 4);
|
|
||||||
for (uint32_t num = 0; num < size; ++num) newPool.append ((char*) &(networkOrder = 0), 4);
|
|
||||||
for (uint32_t num = 0; num < size; ++num) {
|
|
||||||
uint32_t start = newPool.length();
|
|
||||||
if (num < changed.size() && changed[num]) newPool << changes[num];
|
|
||||||
else newPool << original (oldPool ? *oldPool : impl->_pool, num);
|
|
||||||
newPool << '\0';
|
|
||||||
uint32_t valuesOffsetOffset = 4 + num * 4; assert (valuesOffsetOffset < valuesStart);
|
|
||||||
*(uint32_t*)(newPool.data() + valuesOffsetOffset) = htonl (start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
/** Field, old value, new value. Might be used to maintain indexes. */
|
|
||||||
typedef std::function<void(uint32_t, const gstring&, const gstring&)> ChangeVisitor;
|
|
||||||
|
|
||||||
SerializablePoolTpl() = default;
|
|
||||||
/** Copy the given pool bytes from the outside source (e.g. from the database). */
|
|
||||||
SerializablePoolTpl (const gstring& poolBytes): _impl (poolBytes) {}
|
|
||||||
/** Returns a view into the original serialized field (ignores the current changes).\n
|
|
||||||
* Returns an empty string if the field is not in the pool (num > size).
|
|
||||||
* @param ref Return a zero-copy view. The view becomes invalid after the value has been changed or when the pool's `Impl` is destroyed. */
|
|
||||||
const gstring original (uint32_t num, bool ref = false) const {return original (_impl->_pool, num, ref);}
|
|
||||||
/** Returns the original serialized field (ignores the current changes).\n
|
|
||||||
* Returns an empty string if the field is not in the pool (num > size). */
|
|
||||||
const char* cstringOriginal (uint32_t num) const {
|
|
||||||
gstring gs (original (_impl->_pool, num));
|
|
||||||
return gs.empty() ? "" : gs.data(); // All fields in the _pool are 0-terminated.
|
|
||||||
}
|
|
||||||
/** Returns the field.
|
|
||||||
* @param ref Return a zero-copy view. The view becomes invalid after the value has been changed or when the pool's `Impl` is destroyed. */
|
|
||||||
const gstring current (uint32_t num, bool ref = false) const {
|
|
||||||
const Impl* impl = _impl.get(); if (!impl) return gstring();
|
|
||||||
if (num < impl->_changed.size() && impl->_changed[num]) {
|
|
||||||
const gstring& value = impl->_changes[num]; return ref ? value.ref() : value;}
|
|
||||||
return original (impl->_pool, num);
|
|
||||||
}
|
|
||||||
/** Set the new value of the field. */
|
|
||||||
void set (uint32_t num, const gstring& value) {
|
|
||||||
Impl* impl = _impl.instance();
|
|
||||||
if (__builtin_expect (impl->_readOnly, 0)) throw std::runtime_error ("Attempt to modify a read-only SerializablePool");
|
|
||||||
if (num >= impl->_changed.size()) {impl->_changed.resize (num + 1); impl->_changes.resize (num + 1);}
|
|
||||||
impl->_changed[num] = true;
|
|
||||||
impl->_changes[num] = value;
|
|
||||||
}
|
|
||||||
void reserve (uint32_t fields) {
|
|
||||||
Impl* impl = _impl.instance();
|
|
||||||
if (__builtin_expect (impl->_readOnly, 0)) throw std::runtime_error ("Attempt to modify a read-only SerializablePool");
|
|
||||||
impl->_changed.reserve (fields);
|
|
||||||
impl->_changes.reserve (fields);
|
|
||||||
}
|
|
||||||
/** Peek into the pool.\n
|
|
||||||
* Returned reference should not be used after the SerializablePool goes out of scope (and destroyed). */
|
|
||||||
const gstring& originalPool() {return _impl->_pool;}
|
|
||||||
/** Serialize the pool.
|
|
||||||
* @param changeVisitor is called for every field that was really changed (e.g. the bytes differ). */
|
|
||||||
void toBytes (gstring& newPool, ChangeVisitor changeVisitor = ChangeVisitor()) const {
|
|
||||||
if (changeVisitor) {
|
|
||||||
const Impl* impl = _impl.get();
|
|
||||||
const std::vector<bool>& changed = impl->_changed;
|
|
||||||
const std::vector<gstring>& changes = impl->_changes;
|
|
||||||
for (uint32_t num = 0, size = changed.size(); num < size; ++num) if (changed[num]) {
|
|
||||||
const gstring& from = original (impl->_pool, num); const gstring& to = changes[num];
|
|
||||||
if (from != to) changeVisitor (num, from, to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toBytes (newPool, (uint32_t) _impl->_changed.size(), nullptr);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Performs "delta" serialization of the pool: creates a new pool where values which has not changed are copied from the `oldPool`.\n
|
|
||||||
* \code Use case: 1) pools X and Y are loaded from a database by users A and B;
|
|
||||||
* 2) user A changes field 0 in pool X; 3) user B changes field 1 in pool Y;
|
|
||||||
* 4) user A loads `oldPool` from the database, does `toBytesDelta` from pool X and saves to the database;
|
|
||||||
* 5) user B loads `oldPool` from the database, does `toBytesDelta` from pool Y and saves to the database;
|
|
||||||
* result: database contains both changes (field 0 from user A and field 1 from user B). \endcode
|
|
||||||
* @param changeVisitor is called for every field that was changed between the oldPool and the current one.
|
|
||||||
* Returns `false` and leaves `newPool` *empty* if there are no changes found against the `oldPool`.
|
|
||||||
*/
|
|
||||||
bool toBytesDelta (gstring& newPool, const gstring& oldPool, ChangeVisitor changeVisitor = ChangeVisitor()) const {
|
|
||||||
newPool.clear();
|
|
||||||
const Impl* impl = _impl.get();
|
|
||||||
const std::vector<bool>& changed = impl->_changed;
|
|
||||||
const std::vector<gstring>& changes = impl->_changes;
|
|
||||||
bool verifiedChanges = false;
|
|
||||||
for (uint32_t num = 0, size = changed.size(); num < size; ++num) if (changed[num]) {
|
|
||||||
const gstring& from = original (oldPool, num); const gstring& to = changes[num];
|
|
||||||
if (from != to) {
|
|
||||||
verifiedChanges = true;
|
|
||||||
if (changeVisitor) changeVisitor (num, from, to); else break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!verifiedChanges) return false;
|
|
||||||
|
|
||||||
toBytes (newPool, std::max ((uint32_t) changed.size(), poolSize (oldPool)), &oldPool);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/** True if the field has been `set` in this pool instance.\n
|
|
||||||
* NB: Does *not* check if the `set` value is equal to the `original` value or not. */
|
|
||||||
bool changed (uint32_t num) const {const auto& changed = _impl->_changed; return num < changed.size() ? changed[num] : false;}
|
|
||||||
/** True if a field has been `set` in this pool instance. */
|
|
||||||
bool changed() const {return !_impl->_changed.empty();}
|
|
||||||
|
|
||||||
bool operator == (const SerializablePoolTpl<PI>& rhs) const {return _impl.get() == rhs._impl.get();}
|
|
||||||
/** Useful for storing SerializablePool in a map. */
|
|
||||||
intptr_t implId() const {return (intptr_t) _impl.get();}
|
|
||||||
|
|
||||||
/** If set to `true` then modifying the pool will throw an exception.\n
|
|
||||||
* Useful for freezing the pool before sharing it with other threads. */
|
|
||||||
void readOnly (bool ro) {if (_impl) _impl->_readOnly = true;}
|
|
||||||
bool readOnly() const {return (_impl ? _impl->_readOnly : false);}
|
|
||||||
|
|
||||||
/** Number of elements in the pool. Equals to max(num)-1. */
|
|
||||||
uint32_t size() const {
|
|
||||||
Impl* impl = _impl.get(); if (__builtin_expect (!impl, 0)) return 0;
|
|
||||||
return std::max (poolSize (impl->_pool), (uint32_t) impl->_changed.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef _SERIALIZABLEPOOL_NOLDB
|
|
||||||
/** Serialize the `value` with `ldbSerialize` and `set` it to `num`.
|
|
||||||
* @param stackSize is the amount of space to preallocate on stack for the temporary buffer. */
|
|
||||||
template<typename T> void serialize (uint32_t num, const T& value, uint32_t stackSize = 256) {
|
|
||||||
GSTRING_ON_STACK (bytes, stackSize);
|
|
||||||
ldbSerialize (bytes, value);
|
|
||||||
set (num, bytes);
|
|
||||||
}
|
|
||||||
/** If the field is not empty then `ldbDeserialize` it into `value`. */
|
|
||||||
template<typename T> void deserialize (uint32_t num, T& value) const {
|
|
||||||
const gstring& bytes = current (num);
|
|
||||||
if (bytes.length()) ldbDeserialize (current (num), value);
|
|
||||||
}
|
|
||||||
/** Deserialize the `num` field with `ldbDeserialize`, run `visitor` on it, then optionally serialize the field back using `ldbSerialize`.
|
|
||||||
* Example: \code
|
|
||||||
* typedef std::map<std::string, std::string> MyMap;
|
|
||||||
* pool.with<MyMap> (_myMap, [](MyMap& myMap) {myMap["foo"] = "bar"; return true;});
|
|
||||||
* \endcode
|
|
||||||
* @param visitor must return `true` to serialize the field back to the pool.
|
|
||||||
*/
|
|
||||||
template<typename T> void with (uint32_t num, std::function<bool(T&)> visitor) {
|
|
||||||
const gstring& fromBytes = current (num, true);
|
|
||||||
T value; if (fromBytes.length()) ldbDeserialize (fromBytes, value);
|
|
||||||
if (visitor (value)) serialize (num, value, 16 + fromBytes.length() * 2);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
using SerializablePool = SerializablePoolTpl<SerializablePoolHelpers::SharedPtr>;
|
|
||||||
using InlineSerializablePool = SerializablePoolTpl<SerializablePoolHelpers::InlinePtr>;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // _GLIM_SERIALIZABLEPOOL_HPP_INCLUDED
|
|
32
external/glim/TscTimer.hpp
vendored
32
external/glim/TscTimer.hpp
vendored
|
@ -1,32 +0,0 @@
|
||||||
#ifndef _TSC_TIMER_H
|
|
||||||
#define _TSC_TIMER_H
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
extern "C" { // http://en.wikipedia.org/wiki/Rdtsc
|
|
||||||
#if (defined(__GNUC__) || defined(__ICC)) && defined(__i386__)
|
|
||||||
static __inline__ unsigned long long rdTsc(void) {
|
|
||||||
unsigned long long ret;
|
|
||||||
__asm__ __volatile__("rdtsc": "=A" (ret));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
#elif (defined(__GNUC__) || defined(__ICC) || defined(__SUNPRO_C)) && defined(__x86_64__)
|
|
||||||
static __inline__ unsigned long long rdTsc(void) {
|
|
||||||
unsigned a, d;
|
|
||||||
asm volatile("rdtsc" : "=a" (a), "=d" (d));
|
|
||||||
return ((unsigned long long)a) | (((unsigned long long)d) << 32);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
//! CPU cycles timer. Fast, not safe.
|
|
||||||
//! cf. http://en.wikipedia.org/wiki/Rdtsc
|
|
||||||
struct TscTimer {
|
|
||||||
int64_t start;
|
|
||||||
TscTimer (): start (rdTsc()) {}
|
|
||||||
int64_t operator()() const {return rdTsc() - start;}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // _TSC_TIMER_H
|
|
203
external/glim/cbcoro.hpp
vendored
203
external/glim/cbcoro.hpp
vendored
|
@ -1,203 +0,0 @@
|
||||||
/** \file
|
|
||||||
* ucontext-based coroutine library designed to emulate a normal control flow around callbacks. */
|
|
||||||
|
|
||||||
// http://en.wikipedia.org/wiki/Setcontext; man 3 makecontext; man 2 getcontext
|
|
||||||
// http://www.boost.org/doc/libs/1_53_0/libs/context/doc/html/index.html
|
|
||||||
// g++ -std=c++11 -O1 -Wall -g test_cbcoro.cc -pthread && ./a.out
|
|
||||||
|
|
||||||
// NB: There is now a coroutine support in Boost ASIO which can be used to make asynchronous APIs look synchronous in a similar way:
|
|
||||||
// https://svn.boost.org/trac/boost/changeset/84311
|
|
||||||
|
|
||||||
#include <ucontext.h>
|
|
||||||
#include <sys/mman.h> // mmap
|
|
||||||
#include <string.h> // strerror
|
|
||||||
#include <mutex>
|
|
||||||
#include <atomic>
|
|
||||||
#include <valgrind/valgrind.h>
|
|
||||||
#include <glim/exception.hpp>
|
|
||||||
#include <boost/container/flat_map.hpp>
|
|
||||||
#include <boost/container/slist.hpp>
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
/// Simplifies turning callback control flows into normal imperative control flows.
|
|
||||||
class CBCoro {
|
|
||||||
public:
|
|
||||||
/// "Holds" the CBCoro and will delete it when it is no longer used.
|
|
||||||
struct CBCoroPtr {
|
|
||||||
CBCoro* _coro;
|
|
||||||
CBCoroPtr (CBCoro* coro): _coro (coro) {
|
|
||||||
_coro->_users++;
|
|
||||||
}
|
|
||||||
~CBCoroPtr() {
|
|
||||||
if (--_coro->_users <= 0 && _coro->_delete) delete _coro;
|
|
||||||
}
|
|
||||||
CBCoro* operator ->() const {return _coro;}
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr size_t defaultStackSize() {return 512 * 1024;}
|
|
||||||
static constexpr uint8_t defaultCacheSize() {return 2;}
|
|
||||||
protected:
|
|
||||||
typedef boost::container::flat_map<size_t, boost::container::slist<void*> > cache_t;
|
|
||||||
/// The cached stacks; stackSize -> free list.
|
|
||||||
static cache_t& cache() {static cache_t CACHE; return CACHE;}
|
|
||||||
static std::mutex& cacheMutex() {static std::mutex CACHE_MUTEX; return CACHE_MUTEX;}
|
|
||||||
|
|
||||||
ucontext_t _context;
|
|
||||||
ucontext_t* _returnTo;
|
|
||||||
std::recursive_mutex _mutex; ///< This one is locked most of the time.
|
|
||||||
std::atomic_int_fast32_t _users; ///< Counter used by `CBCoroPtr`.
|
|
||||||
bool _delete; ///< Whether the `CBCoroPtr` should `delete` this instance when it is no longer used (default is `true`).
|
|
||||||
bool _invokeFromYield; ///< True if `invokeFromCallback()` was called directly from `yieldForCallback()`.
|
|
||||||
bool _yieldFromInvoke; ///< True if `yieldForCallback()` now runs from `invokeFromCallback()`.
|
|
||||||
uint8_t const _cacheStack; ///< Tells `freeStack()` to cache the stack if the number of cached `#_stackSize` stacks is less than it.
|
|
||||||
void* _stack;
|
|
||||||
size_t const _stackSize; ///< Keeps the size of the stack.
|
|
||||||
|
|
||||||
/// Peek a stack from the cache or allocate one with `mmap` (and register with Valgrind).
|
|
||||||
virtual void allocateStack() {
|
|
||||||
if (_cacheStack) {
|
|
||||||
std::lock_guard<std::mutex> lock (cacheMutex());
|
|
||||||
auto& freeList = cache()[_stackSize];
|
|
||||||
if (!freeList.empty()) {_stack = freeList.front(); freeList.pop_front(); return;}
|
|
||||||
}
|
|
||||||
_stack = mmap (nullptr, _stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_NORESERVE, -1, 0);
|
|
||||||
if (_stack == MAP_FAILED) GTHROW (std::string ("mmap allocation failed: ") + ::strerror (errno));
|
|
||||||
#pragma GCC diagnostic ignored "-Wunused-value"
|
|
||||||
VALGRIND_STACK_REGISTER (_stack, (char*) _stack + _stackSize);
|
|
||||||
}
|
|
||||||
/// Release a stack into the cache or free it with `munmap` (and deregister with Valgrind).
|
|
||||||
virtual void freeStack() {
|
|
||||||
if (_cacheStack) {
|
|
||||||
std::lock_guard<std::mutex> lock (cacheMutex());
|
|
||||||
auto& freeList = cache()[_stackSize];
|
|
||||||
if (freeList.size() < _cacheStack) {freeList.push_front (_stack); _stack = nullptr; return;}
|
|
||||||
}
|
|
||||||
VALGRIND_STACK_DEREGISTER (_stack);
|
|
||||||
if (munmap (_stack, _stackSize)) GTHROW (std::string ("!munmap: ") + ::strerror (errno));;
|
|
||||||
_stack = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepare the coroutine (initialize context, allocate stack and register it with Valgrind).
|
|
||||||
CBCoro (uint8_t cacheStack = defaultCacheSize(), size_t stackSize = defaultStackSize()):
|
|
||||||
_returnTo (nullptr), _users (0), _delete (true), _invokeFromYield (false), _yieldFromInvoke (false),
|
|
||||||
_cacheStack (cacheStack), _stack (nullptr), _stackSize (stackSize) {
|
|
||||||
if (getcontext (&_context)) GTHROW ("!getcontext");
|
|
||||||
allocateStack();
|
|
||||||
_context.uc_stack.ss_sp = _stack;
|
|
||||||
_context.uc_stack.ss_size = stackSize;
|
|
||||||
}
|
|
||||||
virtual ~CBCoro() {
|
|
||||||
freeStack();
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
/// Starts the coroutine on the `_stack` (makecontext, swapcontext), calling the `CBCoro::run`.
|
|
||||||
CBCoroPtr start() {
|
|
||||||
CBCoroPtr ptr (this);
|
|
||||||
ucontext_t back; _context.uc_link = &back;
|
|
||||||
makecontext (&_context, (void(*)()) cbcRun, 1, (intptr_t) this);
|
|
||||||
// Since we have to "return" from inside the `yieldForCallback`,
|
|
||||||
// we're not actually using the `_context.uc_link` and `return`, we use `setcontext (_returnTo)` instead.
|
|
||||||
_returnTo = &back;
|
|
||||||
_mutex.lock();
|
|
||||||
swapcontext (&back, &_context); // Now our stack lives and the caller stack is no longer in control.
|
|
||||||
_mutex.unlock();
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
/// Logs exception thrown from `CBCoro::run`.
|
|
||||||
virtual void log (const std::exception& ex) {
|
|
||||||
std::cerr << "glim::CBCoro, exception: " << ex.what() << std::endl;
|
|
||||||
}
|
|
||||||
static void cbcRun (CBCoro* cbCoro) {
|
|
||||||
try {
|
|
||||||
cbCoro->run();
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
cbCoro->log (ex);
|
|
||||||
}
|
|
||||||
cbCoro->cbcReturn(); // Return the control to the rightful owner, e.g. to a last callback who ran `invokeFromCallback`, or otherwise to `cbcStart`.
|
|
||||||
}
|
|
||||||
/// Relinquish the control to the original owner of the thread, restoring its stack.
|
|
||||||
void cbcReturn() {
|
|
||||||
ucontext_t* returnTo = _returnTo;
|
|
||||||
if (returnTo != nullptr) {_returnTo = nullptr; setcontext (returnTo);}
|
|
||||||
}
|
|
||||||
/// This method is performed on the CBCoro stack, allowing it to be suspended and then reanimated from callbacks.
|
|
||||||
virtual void run() = 0;
|
|
||||||
public:
|
|
||||||
/** Use this method to wrap a return-via-callback code.
|
|
||||||
* For example, the callback code \code
|
|
||||||
* startSomeWork ([=]() {
|
|
||||||
* continueWhenWorkIsFinished();
|
|
||||||
* });
|
|
||||||
* \endcode should be turned into \code
|
|
||||||
* yieldForCallback ([&]() {
|
|
||||||
* startSomeWork ([&]() {
|
|
||||||
* invokeFromCallback();
|
|
||||||
* });
|
|
||||||
* });
|
|
||||||
* continueWhenWorkIsFinished();
|
|
||||||
* \endcode
|
|
||||||
*
|
|
||||||
* Captures the stack, runs the `fun` and relinquish the control to `_returnTo`.\n
|
|
||||||
* This method will never "return" by itself, in order for it to "return" the
|
|
||||||
* `fun` MUST call `invokeFromCallback`, maybe later and from a different stack. */
|
|
||||||
template <typename F> CBCoroPtr yieldForCallback (F fun) {
|
|
||||||
CBCoroPtr ptr (this);
|
|
||||||
_yieldFromInvoke = false;
|
|
||||||
if (getcontext (&_context)) GTHROW ("!getcontext"); // Capture.
|
|
||||||
if (_yieldFromInvoke) {
|
|
||||||
// We're now in the future, revived by the `invokeFromCallback`.
|
|
||||||
// All we do now is "return" to the caller whose stack we captured earlier.
|
|
||||||
} else {
|
|
||||||
// We're still in the present, still have some work to do.
|
|
||||||
fun(); // The `fun` is supposed to do something resulting in the `invokeFromCallback` being called later.
|
|
||||||
if (_invokeFromYield) {
|
|
||||||
// The `fun` used the `invokeFromCallback` directly, not resorting to callbacks, meaning we don't have to do our magick.
|
|
||||||
_invokeFromYield = false;
|
|
||||||
} else {
|
|
||||||
// So, the `fun` took measures to revive us later, it's time for us to go into torpor and return the control to whoever we've borrowed it from.
|
|
||||||
cbcReturn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// To be called from a callback in order to lend the control to CBCoro, continuing it from where it called `yieldForCallback`.
|
|
||||||
CBCoroPtr invokeFromCallback() {
|
|
||||||
CBCoroPtr ptr (this);
|
|
||||||
_mutex.lock(); // Wait for an other-thready `yieldForCallback` to finish.
|
|
||||||
if (_returnTo != nullptr) {
|
|
||||||
// We have not yet "returned" from the `yieldForCallback`,
|
|
||||||
// meaning that the `invokeFromCallback` was executed immediately from inside the `yieldForCallback`.
|
|
||||||
// In that case we must DO NOTHING, we must simply continue running on the current stack.
|
|
||||||
_invokeFromYield = true; // Tells `yieldForCallback` to do nothing.
|
|
||||||
} else {
|
|
||||||
// Revive the CBCoro, letting it continue from where it was suspended in `yieldForCallback`.
|
|
||||||
ucontext_t cbContext; _returnTo = &cbContext; _yieldFromInvoke = true;
|
|
||||||
if (swapcontext (&cbContext, &_context)) GTHROW ("!swapcontext");
|
|
||||||
// NB: When the CBCoro is suspended or exits, the control returns back there and then back to the callback from which we borrowed it.
|
|
||||||
if (_returnTo == &cbContext) _returnTo = nullptr;
|
|
||||||
}
|
|
||||||
_mutex.unlock(); // Other-thready `yieldForCallback` has finished and `cbcReturn`ed here.
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** CBCoro running a given functor.
|
|
||||||
* The functor's first argument must be a CBCoro pointer, like this: \code (new CBCoroForFunctor ([](CBCoro* cbcoro) {}))->start(); \endcode */
|
|
||||||
template <typename FUN> struct CBCoroForFunctor: public CBCoro {
|
|
||||||
FUN _fun;
|
|
||||||
template <typename CFUN> CBCoroForFunctor (CFUN&& fun, uint8_t cacheStack, size_t stackSize): CBCoro (cacheStack, stackSize), _fun (std::forward<CFUN> (fun)) {}
|
|
||||||
virtual void run() {_fun (this);}
|
|
||||||
virtual ~CBCoroForFunctor() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Syntactic sugar: Runs a given functor in a CBCoro instance.
|
|
||||||
* Example: \code glim::cbCoro ([](glim::CBCoro* cbcoro) {}); \endcode
|
|
||||||
* Returns a `CBCoroPtr` to the CBCoro instance holding the `fun` which might be held somewhere in order to delay the deletion of `fun`. */
|
|
||||||
template <typename FUN> inline CBCoro::CBCoroPtr cbCoro (FUN&& fun, uint8_t cacheStack = CBCoro::defaultCacheSize(), size_t stackSize = CBCoro::defaultStackSize()) {
|
|
||||||
return (new CBCoroForFunctor<FUN> (std::forward<FUN> (fun), cacheStack, stackSize))->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
40
external/glim/channel.hpp
vendored
40
external/glim/channel.hpp
vendored
|
@ -1,40 +0,0 @@
|
||||||
#ifndef _GLIM_CHANNEL_INCLUDED
|
|
||||||
#define _GLIM_CHANNEL_INCLUDED
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
/// Unbuffered channel.
|
|
||||||
/// Optimized for a single value (busy-waits on a second one).
|
|
||||||
template <typename V>
|
|
||||||
struct Channel {
|
|
||||||
V _v;
|
|
||||||
std::mutex _mutex; // Locked when there is no value.
|
|
||||||
std::atomic_int_fast8_t _state; enum State {EMPTY = 0, WRITING = 1, FULL = 2};
|
|
||||||
Channel(): _state (EMPTY) {_mutex.lock();}
|
|
||||||
// Waits until the Channel is empty then stores the value.
|
|
||||||
template <typename VA> void send (VA&& v) {
|
|
||||||
for (;;) {
|
|
||||||
int_fast8_t expectEmpty = EMPTY; if (_state.compare_exchange_weak (expectEmpty, WRITING)) break;
|
|
||||||
std::this_thread::sleep_for (std::chrono::milliseconds (20));
|
|
||||||
}
|
|
||||||
try {_v = std::forward<V> (v);} catch (...) {_state = EMPTY; throw;}
|
|
||||||
_state = FULL;
|
|
||||||
_mutex.unlock(); // Allows the reader to proceed.
|
|
||||||
}
|
|
||||||
// Waits untill there is a value to receive.
|
|
||||||
V receive() {
|
|
||||||
_mutex.lock(); // Wait.
|
|
||||||
V tmp = std::move (_v);
|
|
||||||
assert (_state == FULL);
|
|
||||||
_state = EMPTY;
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace glim
|
|
||||||
|
|
||||||
#endif
|
|
304
external/glim/curl.hpp
vendored
304
external/glim/curl.hpp
vendored
|
@ -1,304 +0,0 @@
|
||||||
/** \file
|
|
||||||
* Very simple header-only wrapper around libcurl.\n
|
|
||||||
* See also: https://github.com/venam/Browser\n
|
|
||||||
* See also: https://github.com/mologie/curl-asio\n
|
|
||||||
* See also: http://thread.gmane.org/gmane.comp.web.curl.library/1322 (this one uses a temporary file). */
|
|
||||||
|
|
||||||
#ifndef _GLIM_CURL_INCLUDED
|
|
||||||
#define _GLIM_CURL_INCLUDED
|
|
||||||
|
|
||||||
#include "gstring.hpp"
|
|
||||||
#include "exception.hpp"
|
|
||||||
#include <curl/curl.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <functional>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
inline size_t curlWriteToString (void *buffer, size_t size, size_t nmemb, void *userp) {
|
|
||||||
((std::string*) userp)->append ((const char*) buffer, size * nmemb);
|
|
||||||
return size * nmemb;};
|
|
||||||
|
|
||||||
inline size_t curlReadFromString (void *ptr, size_t size, size_t nmemb, void *userdata);
|
|
||||||
inline size_t curlReadFromGString (void *ptr, size_t size, size_t nmemb, void *userdata);
|
|
||||||
inline size_t curlWriteHeader (void *ptr, size_t size, size_t nmemb, void *curlPtr);
|
|
||||||
inline int curlDebugCB (CURL* curl, curl_infotype type, char* bytes, size_t size, void* curlPtr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Simple HTTP requests using cURL.
|
|
||||||
Example: \code
|
|
||||||
std::string w3 = glim::Curl() .http ("http://www.w3.org/") .go().str();
|
|
||||||
\endcode
|
|
||||||
*/
|
|
||||||
class Curl {
|
|
||||||
protected:
|
|
||||||
Curl (const Curl&): _curl (NULL), _headers (NULL), _sent (0), _needs_cleanup (true) {} // No copying.
|
|
||||||
public:
|
|
||||||
struct PerformError: public glim::Exception {
|
|
||||||
PerformError (const char* message, const char* file, int32_t line):
|
|
||||||
glim::Exception (message, file, line) {}
|
|
||||||
};
|
|
||||||
struct GetinfoError: public glim::Exception {
|
|
||||||
CURLcode _code; std::string _error;
|
|
||||||
GetinfoError (CURLcode code, const std::string& error, const char* file, int32_t line):
|
|
||||||
glim::Exception (error, file, line),
|
|
||||||
_code (code), _error (error) {}
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
CURL* _curl;
|
|
||||||
struct curl_slist *_headers;
|
|
||||||
std::function<void (const char* header, int len)> _headerListener;
|
|
||||||
std::function<void (curl_infotype type, char* bytes, size_t size)> _debugListener;
|
|
||||||
std::string _sendStr; ///< We're using `std::string` instead of `gstring` in order to support payloads larger than 16 MiB.
|
|
||||||
glim::gstring _sendGStr; ///< `gstring::view` and `gstring::ref` allow us to zero-copy.
|
|
||||||
uint32_t _sent;
|
|
||||||
std::string _got;
|
|
||||||
bool _needs_cleanup:1; ///< ~Curl will do `curl_easy_cleanup` if `true`.
|
|
||||||
char _errorBuf[CURL_ERROR_SIZE];
|
|
||||||
|
|
||||||
Curl (Curl&&) = default;
|
|
||||||
|
|
||||||
/// @param cleanup can be turned off if the cURL is freed elsewhere.
|
|
||||||
Curl (bool cleanup = true): _curl (curl_easy_init()), _headers (NULL), _sent (0), _needs_cleanup (cleanup) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_NOSIGNAL, 1L); // required per http://curl.haxx.se/libcurl/c/libcurl-tutorial.html#Multi-threading
|
|
||||||
*_errorBuf = 0;}
|
|
||||||
/// Wraps an existing handle (will invoke `curl_easy_cleanup` nevertheless).
|
|
||||||
/// @param cleanup can be turned off if the cURL is freed elsewhere.
|
|
||||||
Curl (CURL* curl, bool cleanup = true): _curl (curl), _headers (NULL), _sent (0), _needs_cleanup (cleanup) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_NOSIGNAL, 1L); // required per http://curl.haxx.se/libcurl/c/libcurl-tutorial.html#Multi-threading
|
|
||||||
*_errorBuf = 0;}
|
|
||||||
~Curl(){
|
|
||||||
if (_headers) {curl_slist_free_all (_headers); _headers = NULL;}
|
|
||||||
if (_curl) {if (_needs_cleanup) curl_easy_cleanup (_curl); _curl = NULL;}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Stores the content to be sent into an `std::string` inside `Curl`.
|
|
||||||
* NB: In order to have an effect this method should be used *before* the `http()` and `smtp()` methods. */
|
|
||||||
template<typename STR> Curl& send (STR&& text) {
|
|
||||||
_sendStr = std::forward<STR> (text);
|
|
||||||
_sendGStr.clear();
|
|
||||||
_sent = 0;
|
|
||||||
return *this;}
|
|
||||||
|
|
||||||
/// Adds "Content-Type" header into `_headers`.
|
|
||||||
Curl& contentType (const char* ct) {
|
|
||||||
char ctb[64]; gstring cth (sizeof (ctb), ctb, false, 0);
|
|
||||||
cth << "Content-Type: " << ct << "\r\n";
|
|
||||||
_headers = curl_slist_append (_headers, cth.c_str());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @param fullHeader is a full HTTP header and a newline, e.g. "User-Agent: Me\r\n".
|
|
||||||
Curl& header (const char* fullHeader) {
|
|
||||||
_headers = curl_slist_append (_headers, fullHeader);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Sets the majority of options for the http request.
|
|
||||||
NB: If `send` was used with a non-empty string then `http` will use `CURLOPT_UPLOAD`, setting http method to `PUT` (use the `method()` to override).
|
|
||||||
\n
|
|
||||||
Example: \code
|
|
||||||
glim::Curl curl;
|
|
||||||
curl.http (url.c_str()) .go();
|
|
||||||
std::cout << curl.status() << std::endl << curl.str() << std::endl;
|
|
||||||
\endcode
|
|
||||||
*/
|
|
||||||
Curl& http (const char* url, int timeoutSec = 20) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_NOSIGNAL, 1L); // required per http://curl.haxx.se/libcurl/c/libcurl-tutorial.html#Multi-threading
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_URL, url);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_WRITEFUNCTION, curlWriteToString);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_WRITEDATA, &_got);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_TIMEOUT, timeoutSec);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_ERRORBUFFER, _errorBuf);
|
|
||||||
if (_sendStr.size() || _sendGStr.size()) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_UPLOAD, 1L); // http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUPLOAD
|
|
||||||
if (_sendStr.size()) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_INFILESIZE, (long) _sendStr.size());
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_READFUNCTION, curlReadFromString);
|
|
||||||
} else {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_INFILESIZE, (long) _sendGStr.size());
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_READFUNCTION, curlReadFromGString);}
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_READDATA, this);}
|
|
||||||
if (_headers)
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_HTTPHEADER, _headers); // http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHTTPHEADER
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Set options for smtp request.
|
|
||||||
Example: \code
|
|
||||||
long rc = glim::Curl().send ("Subject: subject\r\n\r\n" "text\r\n") .smtp ("from", "to") .go().status();
|
|
||||||
if (rc != 250) std::cerr << "Error sending email: " << rc << std::endl;
|
|
||||||
\endcode */
|
|
||||||
Curl& smtp (const char* from, const char* to) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_NOSIGNAL, 1L); // required per http://curl.haxx.se/libcurl/c/libcurl-tutorial.html#Multi-threading
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_URL, "smtp://127.0.0.1");
|
|
||||||
if (from) curl_easy_setopt (_curl, CURLOPT_MAIL_FROM, from);
|
|
||||||
bcc (to);
|
|
||||||
if (_headers) curl_easy_setopt (_curl, CURLOPT_MAIL_RCPT, _headers);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_WRITEFUNCTION, curlWriteToString);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_WRITEDATA, &_got);
|
|
||||||
if (_sendStr.size()) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_INFILESIZE, (long) _sendStr.size());
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_READFUNCTION, curlReadFromString);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_READDATA, this);
|
|
||||||
} else if (_sendGStr.size()) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_INFILESIZE, (long) _sendGStr.size());
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_READFUNCTION, curlReadFromGString);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_READDATA, this);
|
|
||||||
}
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_UPLOAD, 1L); // cURL now needs this to actually send the email, cf. "http://curl.haxx.se/mail/lib-2013-12/0152.html".
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Add SMTP recipient to the `_headers` (which are then set into `CURLOPT_MAIL_RCPT` by the `Curl::smtp`).
|
|
||||||
* NB: Should be used *before* the `Curl::smtp`! */
|
|
||||||
Curl& bcc (const char* to) {
|
|
||||||
if (to) _headers = curl_slist_append (_headers, to);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Uses `CURLOPT_CUSTOMREQUEST` to set the http method.
|
|
||||||
Can be used both before and after the `http` method.\n
|
|
||||||
Example sending a POST request to ElasticSearch: \code
|
|
||||||
glim::Curl curl;
|
|
||||||
curl.send (C2GSTRING (R"({"query":{"match_all":{}},"facets":{"tags":{"terms":{"field":"tags","size":1000}}}})"));
|
|
||||||
curl.method ("POST") .http ("http://127.0.0.1:9200/froples/frople/_search", 120);
|
|
||||||
if (curl.verbose().go().status() != 200) GTHROW ("Error fetching tags: " + std::to_string (curl.status()) + ", " + curl.str());
|
|
||||||
cout << curl.gstr() << endl;
|
|
||||||
\endcode */
|
|
||||||
Curl& method (const char* method) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_CUSTOMREQUEST, method);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Setup a handler to process the headers cURL gets from the response.
|
|
||||||
* "The header callback will be called once for each header and only complete header lines are passed on to the callback".\n
|
|
||||||
* See http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHEADERFUNCTION */
|
|
||||||
Curl& headerListener (std::function<void (const char* header, int len)> listener) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_HEADERFUNCTION, curlWriteHeader);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_WRITEHEADER, this);
|
|
||||||
_headerListener = listener;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Setup a handler to get the debug messages generated by cURL.
|
|
||||||
* See http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTDEBUGFUNCTION */
|
|
||||||
Curl& debugListener (std::function<void (curl_infotype type, char* bytes, size_t size)> listener) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_DEBUGFUNCTION, curlDebugCB);
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_DEBUGDATA, this);
|
|
||||||
_debugListener = listener;
|
|
||||||
return verbose (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Setup a handler to get some of the debug messages generated by cURL.
|
|
||||||
Listener gets a formatted text: outbound data is prepended with "> " and inbound with "< ".\n
|
|
||||||
Usage example: \code
|
|
||||||
auto curlDebug = std::make_shared<std::string>();
|
|
||||||
curl->debugListenerF ([curlDebug](const char* bytes, size_t size) {curlDebug->append (bytes, size);});
|
|
||||||
...
|
|
||||||
if (curl->status() != 200) std::cerr << "cURL status != 200; debug follows: " << *curlDebug << std::endl;
|
|
||||||
\endcode
|
|
||||||
See http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTDEBUGFUNCTION
|
|
||||||
@param listener The receiver of the debug information.
|
|
||||||
@param data Whether to pass the data (`CURLINFO_DATA_IN`, `CURLINFO_DATA_OUT`) to the `listener`.
|
|
||||||
*/
|
|
||||||
Curl& debugListenerF (std::function<void (const char* bytes, size_t size)> listener, bool data = false) {
|
|
||||||
return debugListener ([listener/* = std::move (listener)*/,data] (curl_infotype type, char* bytes, size_t size) {
|
|
||||||
GSTRING_ON_STACK (buf, 256);
|
|
||||||
auto prepend = [&](const char* prefix) {
|
|
||||||
buf << prefix; for (char *p = bytes, *end = bytes + size; p < end; ++p) {buf << *p; if (*p == '\n' && p + 2 < end) buf << prefix;}};
|
|
||||||
if (type == CURLINFO_HEADER_IN || (type == CURLINFO_DATA_IN && data)) prepend ("< ");
|
|
||||||
else if (type == CURLINFO_HEADER_OUT || (type == CURLINFO_DATA_OUT && data)) prepend ("> ");
|
|
||||||
listener (buf.c_str(), buf.size());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether to print debug information to `CURLOPT_STDERR`.
|
|
||||||
/// Note that when `debugListener` is used, verbose output will go to the listener and not to `CURLOPT_STDERR`.
|
|
||||||
Curl& verbose (bool on = true) {
|
|
||||||
curl_easy_setopt (_curl, CURLOPT_VERBOSE, on ? 1L : 0L);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the buffers and perform the cURL request.
|
|
||||||
Curl& go() {
|
|
||||||
_got.clear();
|
|
||||||
*_errorBuf = 0;
|
|
||||||
if (curl_easy_perform (_curl)) throw PerformError (_errorBuf, __FILE__, __LINE__);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The contents of the response.
|
|
||||||
const std::string& str() const {return _got;}
|
|
||||||
/// CString of `str`.
|
|
||||||
const char* c_str() const {return _got.c_str();}
|
|
||||||
/// Returns a gstring "view" into `str`.
|
|
||||||
gstring gstr() const {return gstring (0, (void*) _got.data(), false, _got.size());}
|
|
||||||
|
|
||||||
/// The status of the response (For HTTP it's 200 ok, 404 not found, 500 error, etc).
|
|
||||||
long status() const {
|
|
||||||
long status; CURLcode err = curl_easy_getinfo (_curl, CURLINFO_RESPONSE_CODE, &status);
|
|
||||||
if (err) {
|
|
||||||
GSTRING_ON_STACK (message, 128) << "CURL error " << (int) err << ": " << curl_easy_strerror (err);
|
|
||||||
throw GetinfoError (err, message.str(), __FILE__, __LINE__);
|
|
||||||
}
|
|
||||||
return status;}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Moves the content to be sent into a `glim::gstring` inside `Curl`.
|
|
||||||
* NB: In order to have an effect this method should be used *before* the `http()` and `smtp()` methods. */
|
|
||||||
template<> inline Curl& Curl::send<gstring> (gstring&& text) {
|
|
||||||
_sendStr.clear();
|
|
||||||
_sendGStr = std::move (text);
|
|
||||||
_sent = 0;
|
|
||||||
return *this;}
|
|
||||||
|
|
||||||
inline size_t curlReadFromString (void *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
||||||
Curl* curl = (Curl*) userdata;
|
|
||||||
size_t len = std::min (curl->_sendStr.size() - curl->_sent, size * nmemb);
|
|
||||||
if (len) memcpy (ptr, curl->_sendStr.data() + curl->_sent, len);
|
|
||||||
curl->_sent += len;
|
|
||||||
return len;}
|
|
||||||
|
|
||||||
inline size_t curlReadFromGString (void *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
||||||
Curl* curl = (Curl*) userdata;
|
|
||||||
size_t len = std::min (curl->_sendGStr.size() - curl->_sent, size * nmemb);
|
|
||||||
if (len) memcpy (ptr, curl->_sendGStr.data() + curl->_sent, len);
|
|
||||||
curl->_sent += len;
|
|
||||||
return len;}
|
|
||||||
|
|
||||||
// http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHEADERFUNCTION
|
|
||||||
inline size_t curlWriteHeader (void *ptr, size_t size, size_t nmemb, void *curlPtr) {
|
|
||||||
Curl* curl = (Curl*) curlPtr;
|
|
||||||
std::function<void (const char* header, int len)>& listener = curl->_headerListener;
|
|
||||||
int len = size * nmemb;
|
|
||||||
if (listener) listener ((const char*) ptr, len);
|
|
||||||
return (size_t) len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTDEBUGFUNCTION
|
|
||||||
inline int curlDebugCB (CURL*, curl_infotype type, char* bytes, size_t size, void* curlPtr) {
|
|
||||||
Curl* curl = (Curl*) curlPtr;
|
|
||||||
auto& listener = curl->_debugListener;
|
|
||||||
if (listener) listener (type, bytes, size);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Example: std::string w3 = glim::curl2str ("http://www.w3.org/");
|
|
||||||
inline std::string curl2str (const char* url, int timeoutSec = 20) {
|
|
||||||
try {
|
|
||||||
return glim::Curl().http (url, timeoutSec) .go().str();
|
|
||||||
} catch (const std::exception&) {}
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
237
external/glim/doxyconf
vendored
237
external/glim/doxyconf
vendored
|
@ -1,237 +0,0 @@
|
||||||
# Doxyfile 1.8.4; http://www.stack.nl/~dimitri/doxygen/manual/config.html
|
|
||||||
DOXYFILE_ENCODING = UTF-8
|
|
||||||
PROJECT_NAME = "libglim"
|
|
||||||
PROJECT_NUMBER = 0.7
|
|
||||||
OUTPUT_DIRECTORY = doc
|
|
||||||
CREATE_SUBDIRS = NO
|
|
||||||
OUTPUT_LANGUAGE = English
|
|
||||||
BRIEF_MEMBER_DESC = YES
|
|
||||||
REPEAT_BRIEF = YES
|
|
||||||
ALWAYS_DETAILED_SEC = NO
|
|
||||||
INLINE_INHERITED_MEMB = NO
|
|
||||||
FULL_PATH_NAMES = NO
|
|
||||||
SHORT_NAMES = NO
|
|
||||||
JAVADOC_AUTOBRIEF = YES
|
|
||||||
QT_AUTOBRIEF = NO
|
|
||||||
MULTILINE_CPP_IS_BRIEF = NO
|
|
||||||
INHERIT_DOCS = YES
|
|
||||||
SEPARATE_MEMBER_PAGES = NO
|
|
||||||
TAB_SIZE = 2
|
|
||||||
OPTIMIZE_OUTPUT_FOR_C = YES
|
|
||||||
OPTIMIZE_OUTPUT_JAVA = NO
|
|
||||||
OPTIMIZE_FOR_FORTRAN = NO
|
|
||||||
OPTIMIZE_OUTPUT_VHDL = NO
|
|
||||||
# http://daringfireball.net/projects/markdown/
|
|
||||||
MARKDOWN_SUPPORT = YES
|
|
||||||
AUTOLINK_SUPPORT = YES
|
|
||||||
BUILTIN_STL_SUPPORT = YES
|
|
||||||
CPP_CLI_SUPPORT = NO
|
|
||||||
SIP_SUPPORT = NO
|
|
||||||
IDL_PROPERTY_SUPPORT = NO
|
|
||||||
DISTRIBUTE_GROUP_DOC = NO
|
|
||||||
SUBGROUPING = YES
|
|
||||||
INLINE_GROUPED_CLASSES = NO
|
|
||||||
INLINE_SIMPLE_STRUCTS = YES
|
|
||||||
TYPEDEF_HIDES_STRUCT = NO
|
|
||||||
LOOKUP_CACHE_SIZE = 0
|
|
||||||
EXTRACT_ALL = NO
|
|
||||||
EXTRACT_PRIVATE = NO
|
|
||||||
EXTRACT_PACKAGE = NO
|
|
||||||
EXTRACT_STATIC = NO
|
|
||||||
EXTRACT_LOCAL_CLASSES = YES
|
|
||||||
EXTRACT_LOCAL_METHODS = NO
|
|
||||||
EXTRACT_ANON_NSPACES = NO
|
|
||||||
HIDE_UNDOC_MEMBERS = YES
|
|
||||||
HIDE_UNDOC_CLASSES = YES
|
|
||||||
HIDE_FRIEND_COMPOUNDS = NO
|
|
||||||
HIDE_IN_BODY_DOCS = NO
|
|
||||||
INTERNAL_DOCS = NO
|
|
||||||
CASE_SENSE_NAMES = YES
|
|
||||||
HIDE_SCOPE_NAMES = NO
|
|
||||||
SHOW_INCLUDE_FILES = YES
|
|
||||||
FORCE_LOCAL_INCLUDES = NO
|
|
||||||
INLINE_INFO = YES
|
|
||||||
SORT_MEMBER_DOCS = YES
|
|
||||||
SORT_BRIEF_DOCS = YES
|
|
||||||
SORT_MEMBERS_CTORS_1ST = NO
|
|
||||||
SORT_GROUP_NAMES = NO
|
|
||||||
SORT_BY_SCOPE_NAME = NO
|
|
||||||
STRICT_PROTO_MATCHING = NO
|
|
||||||
GENERATE_TODOLIST = YES
|
|
||||||
GENERATE_TESTLIST = YES
|
|
||||||
GENERATE_BUGLIST = YES
|
|
||||||
GENERATE_DEPRECATEDLIST= YES
|
|
||||||
MAX_INITIALIZER_LINES = 30
|
|
||||||
SHOW_USED_FILES = YES
|
|
||||||
SHOW_FILES = YES
|
|
||||||
SHOW_NAMESPACES = YES
|
|
||||||
QUIET = NO
|
|
||||||
WARNINGS = YES
|
|
||||||
WARN_IF_UNDOCUMENTED = NO
|
|
||||||
WARN_IF_DOC_ERROR = YES
|
|
||||||
WARN_NO_PARAMDOC = NO
|
|
||||||
WARN_FORMAT = "$file:$line: $text"
|
|
||||||
|
|
||||||
INPUT = ./
|
|
||||||
INPUT_ENCODING = UTF-8
|
|
||||||
FILE_PATTERNS = *.hpp
|
|
||||||
RECURSIVE = NO
|
|
||||||
EXCLUDE =
|
|
||||||
EXCLUDE_SYMLINKS = NO
|
|
||||||
EXCLUDE_PATTERNS =
|
|
||||||
EXCLUDE_SYMBOLS =
|
|
||||||
EXAMPLE_PATH =
|
|
||||||
EXAMPLE_PATTERNS = test_*.cc
|
|
||||||
EXAMPLE_RECURSIVE = NO
|
|
||||||
IMAGE_PATH =
|
|
||||||
FILTER_SOURCE_FILES = NO
|
|
||||||
SOURCE_BROWSER = NO
|
|
||||||
INLINE_SOURCES = NO
|
|
||||||
STRIP_CODE_COMMENTS = NO
|
|
||||||
REFERENCED_BY_RELATION = NO
|
|
||||||
REFERENCES_RELATION = NO
|
|
||||||
REFERENCES_LINK_SOURCE = YES
|
|
||||||
USE_HTAGS = NO
|
|
||||||
VERBATIM_HEADERS = YES
|
|
||||||
CLANG_ASSISTED_PARSING = NO
|
|
||||||
ALPHABETICAL_INDEX = YES
|
|
||||||
COLS_IN_ALPHA_INDEX = 5
|
|
||||||
GENERATE_HTML = YES
|
|
||||||
HTML_OUTPUT = html
|
|
||||||
HTML_FILE_EXTENSION = .html
|
|
||||||
HTML_HEADER =
|
|
||||||
HTML_FOOTER =
|
|
||||||
HTML_STYLESHEET =
|
|
||||||
HTML_EXTRA_STYLESHEET =
|
|
||||||
HTML_EXTRA_FILES =
|
|
||||||
HTML_COLORSTYLE_HUE = 220
|
|
||||||
HTML_COLORSTYLE_SAT = 100
|
|
||||||
HTML_COLORSTYLE_GAMMA = 80
|
|
||||||
HTML_TIMESTAMP = YES
|
|
||||||
HTML_DYNAMIC_SECTIONS = NO
|
|
||||||
HTML_INDEX_NUM_ENTRIES = 100
|
|
||||||
GENERATE_DOCSET = NO
|
|
||||||
DOCSET_FEEDNAME = "Doxygen generated docs"
|
|
||||||
DOCSET_BUNDLE_ID = org.doxygen.Project
|
|
||||||
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
|
|
||||||
DOCSET_PUBLISHER_NAME = Publisher
|
|
||||||
GENERATE_HTMLHELP = NO
|
|
||||||
CHM_FILE =
|
|
||||||
HHC_LOCATION =
|
|
||||||
GENERATE_CHI = NO
|
|
||||||
CHM_INDEX_ENCODING =
|
|
||||||
BINARY_TOC = NO
|
|
||||||
TOC_EXPAND = NO
|
|
||||||
GENERATE_QHP = NO
|
|
||||||
QCH_FILE =
|
|
||||||
QHP_NAMESPACE = org.doxygen.Project
|
|
||||||
QHP_VIRTUAL_FOLDER = doc
|
|
||||||
QHP_CUST_FILTER_NAME =
|
|
||||||
QHP_CUST_FILTER_ATTRS =
|
|
||||||
QHP_SECT_FILTER_ATTRS =
|
|
||||||
QHG_LOCATION =
|
|
||||||
GENERATE_ECLIPSEHELP = NO
|
|
||||||
ECLIPSE_DOC_ID = org.doxygen.Project
|
|
||||||
DISABLE_INDEX = NO
|
|
||||||
GENERATE_TREEVIEW = NO
|
|
||||||
ENUM_VALUES_PER_LINE = 4
|
|
||||||
TREEVIEW_WIDTH = 250
|
|
||||||
EXT_LINKS_IN_WINDOW = NO
|
|
||||||
FORMULA_FONTSIZE = 10
|
|
||||||
FORMULA_TRANSPARENT = YES
|
|
||||||
USE_MATHJAX = NO
|
|
||||||
MATHJAX_FORMAT = HTML-CSS
|
|
||||||
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
|
|
||||||
MATHJAX_EXTENSIONS =
|
|
||||||
MATHJAX_CODEFILE =
|
|
||||||
SEARCHENGINE = YES
|
|
||||||
SERVER_BASED_SEARCH = NO
|
|
||||||
EXTERNAL_SEARCH = NO
|
|
||||||
SEARCHENGINE_URL =
|
|
||||||
SEARCHDATA_FILE = searchdata.xml
|
|
||||||
EXTERNAL_SEARCH_ID =
|
|
||||||
EXTRA_SEARCH_MAPPINGS =
|
|
||||||
GENERATE_LATEX = YES
|
|
||||||
LATEX_OUTPUT = latex
|
|
||||||
LATEX_CMD_NAME = latex
|
|
||||||
MAKEINDEX_CMD_NAME = makeindex
|
|
||||||
COMPACT_LATEX = NO
|
|
||||||
PAPER_TYPE = a4
|
|
||||||
EXTRA_PACKAGES =
|
|
||||||
LATEX_HEADER =
|
|
||||||
LATEX_FOOTER =
|
|
||||||
LATEX_EXTRA_FILES =
|
|
||||||
PDF_HYPERLINKS = YES
|
|
||||||
USE_PDFLATEX = NO
|
|
||||||
LATEX_BATCHMODE = NO
|
|
||||||
LATEX_HIDE_INDICES = NO
|
|
||||||
LATEX_SOURCE_CODE = NO
|
|
||||||
LATEX_BIB_STYLE = plain
|
|
||||||
GENERATE_RTF = NO
|
|
||||||
RTF_OUTPUT = rtf
|
|
||||||
COMPACT_RTF = NO
|
|
||||||
RTF_HYPERLINKS = NO
|
|
||||||
RTF_STYLESHEET_FILE =
|
|
||||||
RTF_EXTENSIONS_FILE =
|
|
||||||
GENERATE_MAN = NO
|
|
||||||
MAN_OUTPUT = man
|
|
||||||
MAN_EXTENSION = .3
|
|
||||||
MAN_LINKS = NO
|
|
||||||
GENERATE_XML = NO
|
|
||||||
XML_OUTPUT = xml
|
|
||||||
XML_SCHEMA =
|
|
||||||
XML_DTD =
|
|
||||||
XML_PROGRAMLISTING = YES
|
|
||||||
GENERATE_DOCBOOK = NO
|
|
||||||
DOCBOOK_OUTPUT = docbook
|
|
||||||
GENERATE_AUTOGEN_DEF = NO
|
|
||||||
GENERATE_PERLMOD = NO
|
|
||||||
PERLMOD_LATEX = NO
|
|
||||||
PERLMOD_PRETTY = YES
|
|
||||||
PERLMOD_MAKEVAR_PREFIX =
|
|
||||||
ENABLE_PREPROCESSING = YES
|
|
||||||
MACRO_EXPANSION = NO
|
|
||||||
EXPAND_ONLY_PREDEF = NO
|
|
||||||
SEARCH_INCLUDES = YES
|
|
||||||
INCLUDE_PATH =
|
|
||||||
INCLUDE_FILE_PATTERNS =
|
|
||||||
PREDEFINED =
|
|
||||||
EXPAND_AS_DEFINED =
|
|
||||||
SKIP_FUNCTION_MACROS = YES
|
|
||||||
TAGFILES =
|
|
||||||
GENERATE_TAGFILE =
|
|
||||||
ALLEXTERNALS = NO
|
|
||||||
EXTERNAL_GROUPS = YES
|
|
||||||
EXTERNAL_PAGES = YES
|
|
||||||
PERL_PATH = /usr/bin/perl
|
|
||||||
CLASS_DIAGRAMS = YES
|
|
||||||
MSCGEN_PATH =
|
|
||||||
HIDE_UNDOC_RELATIONS = YES
|
|
||||||
HAVE_DOT = NO
|
|
||||||
DOT_NUM_THREADS = 0
|
|
||||||
DOT_FONTNAME = Helvetica
|
|
||||||
DOT_FONTSIZE = 10
|
|
||||||
DOT_FONTPATH =
|
|
||||||
CLASS_GRAPH = YES
|
|
||||||
COLLABORATION_GRAPH = YES
|
|
||||||
GROUP_GRAPHS = YES
|
|
||||||
UML_LOOK = NO
|
|
||||||
UML_LIMIT_NUM_FIELDS = 10
|
|
||||||
TEMPLATE_RELATIONS = NO
|
|
||||||
INCLUDE_GRAPH = YES
|
|
||||||
INCLUDED_BY_GRAPH = YES
|
|
||||||
CALL_GRAPH = NO
|
|
||||||
CALLER_GRAPH = NO
|
|
||||||
GRAPHICAL_HIERARCHY = YES
|
|
||||||
DIRECTORY_GRAPH = YES
|
|
||||||
DOT_IMAGE_FORMAT = png
|
|
||||||
INTERACTIVE_SVG = NO
|
|
||||||
DOT_PATH =
|
|
||||||
DOTFILE_DIRS =
|
|
||||||
MSCFILE_DIRS =
|
|
||||||
DOT_GRAPH_MAX_NODES = 50
|
|
||||||
MAX_DOT_GRAPH_DEPTH = 0
|
|
||||||
DOT_TRANSPARENT = NO
|
|
||||||
DOT_MULTI_TARGETS = NO
|
|
||||||
GENERATE_LEGEND = YES
|
|
||||||
DOT_CLEANUP = YES
|
|
259
external/glim/exception.hpp
vendored
259
external/glim/exception.hpp
vendored
|
@ -1,259 +0,0 @@
|
||||||
#ifndef _GLIM_EXCEPTION_HPP_INCLUDED
|
|
||||||
#define _GLIM_EXCEPTION_HPP_INCLUDED
|
|
||||||
|
|
||||||
/// \file
|
|
||||||
/// Exceptions with configurable behaviour.
|
|
||||||
/// Requires `thread_local` support introduced in [gcc-4.8](http://gcc.gnu.org/gcc-4.8/changes.html)
|
|
||||||
/// (`__thread` is not reliable with GCC 4.7.2 across shared libraries).
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h> // free
|
|
||||||
#include <unistd.h> // write
|
|
||||||
|
|
||||||
/// Throws `::glim::Exception` passing the current file and line into constructor.
|
|
||||||
#define GTHROW(message) throw ::glim::Exception (message, __FILE__, __LINE__)
|
|
||||||
/// Throws a `::glim::Exception` derived exception `name` passing the current file and line into constructor.
|
|
||||||
#define GNTHROW(name, message) throw name (message, __FILE__, __LINE__)
|
|
||||||
/// Helps defining new `::glim::Exception`-based exceptions.
|
|
||||||
/// Named exceptions might be useful in a debugger.
|
|
||||||
#define G_DEFINE_EXCEPTION(name) \
|
|
||||||
struct name: public ::glim::Exception { \
|
|
||||||
name (const ::std::string& message, const char* file, int line): ::glim::Exception (message, file, line) {} \
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround to compile under GCC 4.7.
|
|
||||||
#if defined (__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 7 && !defined (thread_local)
|
|
||||||
# define thread_local __thread
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
// Ideas:
|
|
||||||
// RAII control via thread-local integer (with bits): option to capture stack trace (printed on "what()")
|
|
||||||
// see http://stacktrace.svn.sourceforge.net/viewvc/stacktrace/stacktrace/call_stack_gcc.cpp?revision=40&view=markup
|
|
||||||
// A handler to log exception with VALGRIND (with optional trace)
|
|
||||||
// A handler to log thread id and *pause* the thread in exception constructor (user can attach GDB and investigate)
|
|
||||||
// (or we might call an empty function: "I once used something similar,
|
|
||||||
// but with an empty function debug_breakpoint. When debugging, I simply entered "bre debug_breakpoint"
|
|
||||||
// at the gdb prompt - no asembler needed (compile debug_breakpoint in a separate compilation unit to avoid having the call optimized away)."
|
|
||||||
// - http://stackoverflow.com/a/4720403/257568)
|
|
||||||
// A handler to call a debugger? (see: http://stackoverflow.com/a/4732119/257568)
|
|
||||||
|
|
||||||
// todo: Try a helper which uses cairo's backtrace-symbols.c
|
|
||||||
// http://code.ohloh.net/file?fid=zUOUdEl-Id-ijyPOmCkVnBJt2d8&cid=zGpizbyIjEw&s=addr2line&browser=Default#L7
|
|
||||||
|
|
||||||
// todo: Try a helper which uses cairo's lookup-symbol.c
|
|
||||||
// http://code.ohloh.net/file?fid=Je2jZqsOxge_SvWVrvywn2I0TIs&cid=zGpizbyIjEw&s=addr2line&browser=Default#L0
|
|
||||||
|
|
||||||
// todo: A helper converting backtrace to addr2line invocation, e.g.
|
|
||||||
// bin/test_exception() [0x4020cc];bin/test_exception(__cxa_throw+0x47) [0x402277];bin/test_exception() [0x401c06];/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd) [0x57f0ead];bin/test_exception() [0x401fd1];
|
|
||||||
// should be converted to
|
|
||||||
// addr2line -pifCa -e bin/test_exception 0x4020cc 0x402277 0x401c06 0x57f0ead 0x401fd1
|
|
||||||
//
|
|
||||||
// The helper should read the shared library addresses from /proc/.../map and generate separate addr2line invocations
|
|
||||||
// for groups of addresses inside the same shared library.
|
|
||||||
// => dladdr instead of /proc/../map; http://stackoverflow.com/a/2606152/257568
|
|
||||||
//
|
|
||||||
// Shared libraries (http://stackoverflow.com/a/7557756/257568).
|
|
||||||
// Example, backtrace: /usr/local/lib/libfrople.so(_ZN5mongo14BSONObjBuilder8appendAsERKNS_11BSONElementERKNS_10StringDataE+0x1ca) [0x2aef5b45eb8a]
|
|
||||||
// cat /proc/23630/maps | grep libfrople
|
|
||||||
// -> 2aef5b363000-2aef5b53e000
|
|
||||||
// 0x2aef5b45eb8a - 2aef5b363000 = FBB8A
|
|
||||||
// addr2line -pifCa -e /usr/local/lib/libfrople.so 0xFBB8A
|
|
||||||
//
|
|
||||||
// cat /proc/`pidof FropleAndImg2`/maps | grep libfrople
|
|
||||||
// addr2line -pifCa -e /usr/local/lib/libfrople.so `perl -e 'printf ("%x", 0x2aef5b45eb8a - 0x2aef5b363000)'`
|
|
||||||
|
|
||||||
inline void captureBacktrace (void* stdStringPtr);
|
|
||||||
|
|
||||||
typedef void (*exception_handler_fn)(void*);
|
|
||||||
|
|
||||||
/// Exception with file and line information and optional stack trace capture.
|
|
||||||
/// Requires `thread_local` support ([gcc-4.8](http://gcc.gnu.org/gcc-4.8/changes.html)).
|
|
||||||
class Exception: public std::runtime_error {
|
|
||||||
protected:
|
|
||||||
const char* _file; int32_t _line;
|
|
||||||
std::string _what;
|
|
||||||
uint32_t _options;
|
|
||||||
|
|
||||||
/// Append [{file}:{line}] into `buf`.
|
|
||||||
void appendLine (std::string& buf) const {
|
|
||||||
if (_file || _line > 0) {
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << '[';
|
|
||||||
if (_file) oss << _file;
|
|
||||||
if (_line >= 0) oss << ':' << _line;
|
|
||||||
oss << "] ";
|
|
||||||
buf.append (oss.str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append a stack trace to `_what`.
|
|
||||||
void capture() {
|
|
||||||
if (_options & RENDEZVOUS) rendezvous();
|
|
||||||
if (_options & CAPTURE_TRACE) {
|
|
||||||
appendLine (_what);
|
|
||||||
_what += "[at ";
|
|
||||||
captureBacktrace (&_what);
|
|
||||||
_what.append ("] ");
|
|
||||||
_what += std::runtime_error::what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
/** The reference to the thread-local options. */
|
|
||||||
inline static uint32_t& options() {
|
|
||||||
static thread_local uint32_t EXCEPTION_OPTIONS = 0;
|
|
||||||
return EXCEPTION_OPTIONS;
|
|
||||||
}
|
|
||||||
enum Options: uint32_t {
|
|
||||||
PLAIN_WHAT = 1, ///< Pass `what` as is, do not add any information to it.
|
|
||||||
HANDLE_ALL = 1 << 1, ///< Run the custom handler from `__cxa_throw`.
|
|
||||||
CAPTURE_TRACE = 1 << 2, ///< Append a stack trace into the `Exception::_what` (with the help of the `captureBacktrace`).
|
|
||||||
RENDEZVOUS = 1 << 3 ///< Call the rendezvous function in `throw` and in `what`, so that the GDB can catch it (break glim::Exception::rendezvous).
|
|
||||||
};
|
|
||||||
|
|
||||||
/** The pointer to the thread-local exception handler. */
|
|
||||||
inline static exception_handler_fn* handler() {
|
|
||||||
static thread_local exception_handler_fn EXCEPTION_HANDLER = nullptr;
|
|
||||||
return &EXCEPTION_HANDLER;
|
|
||||||
}
|
|
||||||
/** The pointer to the thread-local argument for the exception handler. */
|
|
||||||
inline static void** handlerArg() {
|
|
||||||
static thread_local void* EXCEPTION_HANDLER_ARG = nullptr;
|
|
||||||
return &EXCEPTION_HANDLER_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoked when the `RENDEZVOUS` option is set in order to help the debugger catch the exception (break glim::Exception::rendezvous).
|
|
||||||
static void rendezvous() __attribute__((noinline)) {
|
|
||||||
asm (""); // Prevents the function from being optimized away.
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception (const std::string& message):
|
|
||||||
std::runtime_error (message), _file (0), _line (-1), _options (options()) {
|
|
||||||
capture();}
|
|
||||||
Exception (const std::string& message, const char* file, int32_t line):
|
|
||||||
std::runtime_error (message), _file (file), _line (line), _options (options()) {
|
|
||||||
capture();}
|
|
||||||
~Exception() throw() {}
|
|
||||||
virtual const char* what() const throw() {
|
|
||||||
if (_options & RENDEZVOUS) rendezvous();
|
|
||||||
if (_options & PLAIN_WHAT) return std::runtime_error::what();
|
|
||||||
std::string& buf = const_cast<std::string&> (_what);
|
|
||||||
if (buf.empty()) {
|
|
||||||
appendLine (buf);
|
|
||||||
buf.append (std::runtime_error::what());
|
|
||||||
}
|
|
||||||
return buf.c_str();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// RAII control of thrown `Exception`s.
|
|
||||||
/// Example: \code
|
|
||||||
/// glim::ExceptionControl trace (glim::Exception::Options::CAPTURE_TRACE);
|
|
||||||
/// \endcode
|
|
||||||
/// Modifies the `Exception` options via a thread-local variable and restores them back upon destruction.\n
|
|
||||||
/// Currently uses http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Thread_002dLocal.html
|
|
||||||
/// (might use C++11 `thread_local` in the future).
|
|
||||||
class ExceptionControl {
|
|
||||||
protected:
|
|
||||||
uint32_t _savedOptions;
|
|
||||||
public:
|
|
||||||
ExceptionControl (uint32_t newOptions) {
|
|
||||||
uint32_t& options = Exception::options();
|
|
||||||
_savedOptions = options;
|
|
||||||
options = newOptions;
|
|
||||||
}
|
|
||||||
~ExceptionControl() {
|
|
||||||
Exception::options() = _savedOptions;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ExceptionHandler {
|
|
||||||
protected:
|
|
||||||
uint32_t _savedOptions;
|
|
||||||
exception_handler_fn _savedHandler;
|
|
||||||
void* _savedHandlerArg;
|
|
||||||
public:
|
|
||||||
ExceptionHandler (uint32_t newOptions, exception_handler_fn handler, void* handlerArg) {
|
|
||||||
uint32_t& options = Exception::options(); _savedOptions = options; options = newOptions;
|
|
||||||
exception_handler_fn* handler_ = Exception::handler(); _savedHandler = *handler_; *handler_ = handler;
|
|
||||||
void** handlerArg_ = Exception::handlerArg(); _savedHandlerArg = *handlerArg_; *handlerArg_ = handlerArg;
|
|
||||||
}
|
|
||||||
~ExceptionHandler() {
|
|
||||||
Exception::options() = _savedOptions;
|
|
||||||
*Exception::handler() = _savedHandler;
|
|
||||||
*Exception::handlerArg() = _savedHandlerArg;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace glim
|
|
||||||
|
|
||||||
#if defined(__GNUC__) && (defined (__linux__) || defined (_SYSTYPE_BSD))
|
|
||||||
# include <execinfo.h> // backtrace; http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
|
|
||||||
# define _GLIM_USE_EXECINFO
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
/** If `stdStringPtr` is not null then backtrace is saved there (must point to an std::string instance),
|
|
||||||
* otherwise printed to write(2). */
|
|
||||||
void captureBacktrace (void* stdStringPtr) {
|
|
||||||
#ifdef _GLIM_USE_EXECINFO
|
|
||||||
const int arraySize = 10; void *array[arraySize];
|
|
||||||
int got = ::backtrace (array, arraySize);
|
|
||||||
if (stdStringPtr) {
|
|
||||||
std::string* out = (std::string*) stdStringPtr;
|
|
||||||
char **strings = ::backtrace_symbols (array, got);
|
|
||||||
for (int tn = 0; tn < got; ++tn) {out->append (strings[tn]); out->append (1, ';');}
|
|
||||||
::free (strings);
|
|
||||||
} else ::backtrace_symbols_fd (array, got, 2);
|
|
||||||
#else
|
|
||||||
# warning captureBacktrace: I do not know how to capture backtrace there. Patches welcome.
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace glim
|
|
||||||
|
|
||||||
#endif // _GLIM_EXCEPTION_HPP_INCLUDED
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Special handler for ALL exceptions. Usage:
|
|
||||||
* 1) In the `main` module inject this code with:
|
|
||||||
* #define _GLIM_ALL_EXCEPTIONS_CODE
|
|
||||||
* #include <glim/exception.hpp>
|
|
||||||
* 2) Link with "-ldl" (for `dlsym`).
|
|
||||||
* 3) Use the ExceptionHandler to enable special behaviour in the current thread:
|
|
||||||
* glim::ExceptionHandler traceExceptions (glim::Exception::Options::HANDLE_ALL, glim::captureBacktrace, nullptr);
|
|
||||||
*
|
|
||||||
* About handing all exceptions see:
|
|
||||||
* http://stackoverflow.com/a/11674810/257568
|
|
||||||
* http://blog.sjinks.pro/c-cpp/969-track-uncaught-exceptions/
|
|
||||||
*/
|
|
||||||
#ifdef _GLIM_ALL_EXCEPTIONS_CODE
|
|
||||||
|
|
||||||
#include <dlfcn.h> // dlsym
|
|
||||||
|
|
||||||
typedef void(*cxa_throw_type)(void*, void*, void(*)(void*)); // Tested with GCC 4.7.
|
|
||||||
static cxa_throw_type NATIVE_CXA_THROW = 0;
|
|
||||||
|
|
||||||
extern "C" void __cxa_throw (void* thrown_exception, void* tinfo, void (*dest)(void*)) {
|
|
||||||
if (!NATIVE_CXA_THROW) NATIVE_CXA_THROW = reinterpret_cast<cxa_throw_type> (::dlsym (RTLD_NEXT, "__cxa_throw"));
|
|
||||||
if (!NATIVE_CXA_THROW) ::std::terminate();
|
|
||||||
|
|
||||||
using namespace glim;
|
|
||||||
uint32_t options = Exception::options();
|
|
||||||
if (options & Exception::RENDEZVOUS) Exception::rendezvous();
|
|
||||||
if (options & Exception::HANDLE_ALL) {
|
|
||||||
exception_handler_fn handler = *Exception::handler();
|
|
||||||
if (handler) handler (*Exception::handlerArg());
|
|
||||||
}
|
|
||||||
|
|
||||||
NATIVE_CXA_THROW (thrown_exception, tinfo, dest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef _GLIM_ALL_EXCEPTIONS_CODE
|
|
||||||
#endif // _GLIM_ALL_EXCEPTIONS_CODE
|
|
578
external/glim/gstring.hpp
vendored
578
external/glim/gstring.hpp
vendored
|
@ -1,578 +0,0 @@
|
||||||
#ifndef _GSTRING_INCLUDED
|
|
||||||
#define _GSTRING_INCLUDED
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A C++ char string.\n
|
|
||||||
* Can reuse (stack-allocated) buffers.\n
|
|
||||||
* Can create zero-copy views.
|
|
||||||
* @code
|
|
||||||
Copyright 2012 Kozarezov Artem Aleksandrovich
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
* @endcode
|
|
||||||
* @file
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h> // malloc, realloc, free
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h> // memcpy, memmem
|
|
||||||
#include <stdio.h> // snprintf
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <iostream>
|
|
||||||
#include <iterator>
|
|
||||||
|
|
||||||
#include "exception.hpp"
|
|
||||||
|
|
||||||
/// Make a read-only gstring from a C string: `const gstring foo = C2GSTRING("foo")`.
|
|
||||||
#define C2GSTRING(CSTR) ::glim::gstring (::glim::gstring::ReferenceConstructor(), CSTR, sizeof (CSTR) - 1, true)
|
|
||||||
/// Usage: GSTRING_ON_STACK (buf, 64) << "foo" << "bar";
|
|
||||||
#define GSTRING_ON_STACK(NAME, SIZE) char NAME##Buf[SIZE]; ::glim::gstring NAME (SIZE, NAME##Buf, false, 0); NAME.self()
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Based on: C++ version 0.4 char* style "itoa": Written by Lukás Chmela, http://www.strudel.org.uk/itoa/ (GPLv3).
|
|
||||||
* Returns a pointer to the end of the string.
|
|
||||||
* NB about `inline`: http://stackoverflow.com/a/1759575/257568
|
|
||||||
* @param base Maximum is 36 (see http://en.wikipedia.org/wiki/Base_36).
|
|
||||||
*/
|
|
||||||
inline char* itoa (char* ptr, int64_t value, const int base = 10) {
|
|
||||||
// check that the base is valid
|
|
||||||
if (base < 2 || base > 36) {*ptr = '\0'; return ptr;}
|
|
||||||
|
|
||||||
char *ptr1 = ptr;
|
|
||||||
int64_t tmp_value;
|
|
||||||
|
|
||||||
do {
|
|
||||||
tmp_value = value;
|
|
||||||
value /= base;
|
|
||||||
*ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)];
|
|
||||||
} while (value);
|
|
||||||
|
|
||||||
// Apply negative sign
|
|
||||||
if (tmp_value < 0) *ptr++ = '-';
|
|
||||||
char* end = ptr;
|
|
||||||
*ptr-- = '\0';
|
|
||||||
char tmp_char;
|
|
||||||
while (ptr1 < ptr) {
|
|
||||||
tmp_char = *ptr;
|
|
||||||
*ptr--= *ptr1;
|
|
||||||
*ptr1++ = tmp_char;
|
|
||||||
}
|
|
||||||
return end;
|
|
||||||
}
|
|
||||||
|
|
||||||
class gstring_stream;
|
|
||||||
|
|
||||||
class gstring {
|
|
||||||
enum Flags {
|
|
||||||
FREE_FLAG = 0x80000000, // 1st bit; `_buf` needs `free`ing
|
|
||||||
FREE_OFFSET = 31,
|
|
||||||
REF_FLAG = 0x40000000, // 2nd bit; `_buf` has an extended life-time (such as C string literals) and can be shared (passed by reference)
|
|
||||||
REF_OFFSET = 30,
|
|
||||||
CAPACITY_MASK = 0x3F000000, // 3..8 bits; `_buf` size is 2^this
|
|
||||||
CAPACITY_OFFSET = 24,
|
|
||||||
LENGTH_MASK = 0x00FFFFFF, // 9th bit; allocated capacity
|
|
||||||
};
|
|
||||||
uint32_t _meta;
|
|
||||||
public:
|
|
||||||
void* _buf;
|
|
||||||
public:
|
|
||||||
constexpr gstring() noexcept: _meta (0), _buf (nullptr) {}
|
|
||||||
/**
|
|
||||||
* Reuse `buf` of size `bufSize`.
|
|
||||||
* To fully use `buf` the `bufSize` should be the power of two.
|
|
||||||
* @param bufSize The size of the memory allocated to `buf`.
|
|
||||||
* @param buf The memory region to be reused.
|
|
||||||
* @param free Whether the `buf` should be `free`d on resize or gstring destruction.
|
|
||||||
* @param length String length inside the `buf`.
|
|
||||||
* @param ref If true then the `buf` isn't copied by gstring's copy constructors.
|
|
||||||
* This is useful for wrapping C string literals.
|
|
||||||
*/
|
|
||||||
explicit gstring (uint32_t bufSize, void* buf, bool free, uint32_t length, bool ref = false) noexcept {
|
|
||||||
uint32_t power = 0; while (((uint32_t) 1 << (power + 1)) <= bufSize) ++power;
|
|
||||||
_meta = ((uint32_t) free << FREE_OFFSET) |
|
|
||||||
((uint32_t) ref << REF_OFFSET) |
|
|
||||||
(power << CAPACITY_OFFSET) |
|
|
||||||
(length & LENGTH_MASK);
|
|
||||||
_buf = buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ReferenceConstructor {};
|
|
||||||
/// Make a view to the given cstring.
|
|
||||||
/// @param buf The memory region to be reused.
|
|
||||||
/// @param length String length inside the `buf`.
|
|
||||||
/// @param ref If true then the `buf` isn't copied by gstring's copy constructors.
|
|
||||||
/// This is useful for wrapping C string literals.
|
|
||||||
explicit constexpr gstring (ReferenceConstructor, const char* buf, uint32_t length, bool ref = false) noexcept:
|
|
||||||
_meta (((uint32_t) ref << REF_OFFSET) | (length & LENGTH_MASK)), _buf ((void*) buf) {}
|
|
||||||
|
|
||||||
/// Copy the characters into `gstring`.
|
|
||||||
gstring (const char* chars): _meta (0), _buf (nullptr) {
|
|
||||||
if (chars && *chars) {
|
|
||||||
size_t length = ::strlen (chars);
|
|
||||||
_buf = ::malloc (length);
|
|
||||||
::memcpy (_buf, chars, length);
|
|
||||||
_meta = (uint32_t) FREE_FLAG |
|
|
||||||
(length & LENGTH_MASK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy the characters into `gstring`.
|
|
||||||
gstring (const char* chars, size_t length) {
|
|
||||||
if (length != 0) {
|
|
||||||
_buf = ::malloc (length);
|
|
||||||
::memcpy (_buf, chars, length);
|
|
||||||
} else _buf = nullptr;
|
|
||||||
_meta = (uint32_t) FREE_FLAG |
|
|
||||||
(length & LENGTH_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy into `gstring`.
|
|
||||||
gstring (const std::string& str): _meta (0), _buf (nullptr) {
|
|
||||||
if (!str.empty()) {
|
|
||||||
_buf = ::malloc (str.length());
|
|
||||||
::memcpy (_buf, str.data(), str.length());
|
|
||||||
_meta = (uint32_t) FREE_FLAG |
|
|
||||||
(str.length() & LENGTH_MASK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `gstr` is `copiedByReference` then make a shallow copy of it,
|
|
||||||
/// otherwise copy `gstr` contents into a `malloc`ed buffer.
|
|
||||||
gstring (const gstring& gstr) {
|
|
||||||
uint32_t glen = gstr.length();
|
|
||||||
if (glen != 0) {
|
|
||||||
if (gstr.copiedByReference()) {
|
|
||||||
_meta = gstr._meta; _buf = gstr._buf;
|
|
||||||
} else {
|
|
||||||
_buf = ::malloc (glen);
|
|
||||||
if (!_buf) GTHROW ("!malloc");
|
|
||||||
::memcpy (_buf, gstr._buf, glen);
|
|
||||||
_meta = (uint32_t) FREE_FLAG |
|
|
||||||
(glen & LENGTH_MASK);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_meta = 0; _buf = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gstring (gstring&& gstr) noexcept: _meta (gstr._meta), _buf (gstr._buf) {
|
|
||||||
gstr._meta = 0; gstr._buf = nullptr;
|
|
||||||
}
|
|
||||||
gstring& operator = (const gstring& gstr) {
|
|
||||||
// cf. http://stackoverflow.com/questions/9322174/move-assignment-operator-and-if-this-rhs
|
|
||||||
if (this != &gstr) {
|
|
||||||
uint32_t glen = gstr.length();
|
|
||||||
uint32_t power = 0;
|
|
||||||
uint32_t capacity = this->capacity();
|
|
||||||
if (glen <= capacity && capacity > 1) { // `capacity <= 1` means there is no _buf.
|
|
||||||
// We reuse existing buffer. Keep capacity info.
|
|
||||||
power = (_meta & CAPACITY_MASK) >> CAPACITY_OFFSET;
|
|
||||||
} else {
|
|
||||||
if (_buf != nullptr && needsFreeing()) ::free (_buf);
|
|
||||||
if (gstr.copiedByReference()) {
|
|
||||||
_meta = gstr._meta; _buf = gstr._buf;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
_buf = ::malloc (glen);
|
|
||||||
if (_buf == nullptr) GTHROW ("malloc failed");
|
|
||||||
}
|
|
||||||
::memcpy (_buf, gstr._buf, glen);
|
|
||||||
_meta = (uint32_t) FREE_FLAG |
|
|
||||||
(power << CAPACITY_OFFSET) |
|
|
||||||
(glen & LENGTH_MASK);
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
gstring& operator = (gstring&& gstr) noexcept {
|
|
||||||
assert (this != &gstr);
|
|
||||||
if (_buf != nullptr && needsFreeing()) free (_buf);
|
|
||||||
_meta = gstr._meta; _buf = gstr._buf;
|
|
||||||
gstr._meta = 0; gstr._buf = nullptr;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a copy of the string.
|
|
||||||
gstring clone() const {return gstring (data(), length());}
|
|
||||||
/// If the gstring's buffer is not owned then copy the bytes into the owned one.
|
|
||||||
/// Useful for turning a stack-allocated gstring into a heap-allocated gstring.
|
|
||||||
gstring& owned() {if (!needsFreeing()) *this = gstring (data(), length()); return *this;}
|
|
||||||
/** Returns a reference to the gstring: when the reference is copied the internal buffer is not copied but referenced (shallow copy).\n
|
|
||||||
* This method should only be used if it is know that the life-time of the reference and its copies is less than the life-time of the buffer. */
|
|
||||||
gstring ref() const noexcept {return gstring (0, _buf, false, length(), true);}
|
|
||||||
|
|
||||||
bool needsFreeing() const noexcept {return _meta & FREE_FLAG;}
|
|
||||||
bool copiedByReference() const noexcept {return _meta & REF_FLAG;}
|
|
||||||
/// Current buffer capacity (memory allocated to the string). Returns 1 if no memory allocated.
|
|
||||||
uint32_t capacity() const noexcept {return 1 << ((_meta & CAPACITY_MASK) >> CAPACITY_OFFSET);}
|
|
||||||
uint32_t length() const noexcept {return _meta & LENGTH_MASK;}
|
|
||||||
size_t size() const noexcept {return _meta & LENGTH_MASK;}
|
|
||||||
bool empty() const noexcept {return (_meta & LENGTH_MASK) == 0;}
|
|
||||||
std::string str() const {size_t len = size(); return len ? std::string ((const char*) _buf, len) : std::string();}
|
|
||||||
/// NB: might move the string to a new buffer.
|
|
||||||
const char* c_str() const {
|
|
||||||
uint32_t len = length(); if (len == 0) return "";
|
|
||||||
uint32_t cap = capacity();
|
|
||||||
// c_str should work even for const gstring's, otherwise it's too much of a pain.
|
|
||||||
if (cap < len + 1) const_cast<gstring*> (this) ->reserve (len + 1);
|
|
||||||
char* buf = (char*) _buf; buf[len] = 0; return buf;
|
|
||||||
}
|
|
||||||
bool equals (const char* cstr) const noexcept {
|
|
||||||
const char* cstr_; uint32_t clen_;
|
|
||||||
if (cstr != nullptr) {cstr_ = cstr; clen_ = strlen (cstr);} else {cstr_ = ""; clen_ = 0;}
|
|
||||||
const uint32_t len = length();
|
|
||||||
if (len != clen_) return false;
|
|
||||||
const char* gstr_ = _buf != nullptr ? (const char*) _buf : "";
|
|
||||||
return memcmp (gstr_, cstr_, len) == 0;
|
|
||||||
}
|
|
||||||
bool equals (const gstring& gs) const noexcept {
|
|
||||||
uint32_t llen = length(), olen = gs.length();
|
|
||||||
if (llen != olen) return false;
|
|
||||||
return memcmp ((const char*) _buf, (const char*) gs._buf, llen) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char& operator[] (unsigned index) noexcept {return ((char*)_buf)[index];}
|
|
||||||
const char& operator[] (unsigned index) const noexcept {return ((const char*)_buf)[index];}
|
|
||||||
|
|
||||||
/// Access `_buf` as `char*`. `_buf` might be nullptr.
|
|
||||||
char* data() noexcept {return (char*)_buf;}
|
|
||||||
const char* data() const noexcept {return (const char*)_buf;}
|
|
||||||
|
|
||||||
char* endp() noexcept {return (char*)_buf + length();}
|
|
||||||
const char* endp() const noexcept {return (const char*)_buf + length();}
|
|
||||||
|
|
||||||
gstring view (uint32_t pos, int32_t count = -1) noexcept {
|
|
||||||
return gstring (0, data() + pos, false, count >= 0 ? count : length() - pos, copiedByReference());}
|
|
||||||
const gstring view (uint32_t pos, int32_t count = -1) const noexcept {
|
|
||||||
return gstring (0, (void*)(data() + pos), false, count >= 0 ? count : length() - pos, copiedByReference());}
|
|
||||||
|
|
||||||
// http://en.cppreference.com/w/cpp/concept/Iterator
|
|
||||||
template<typename CT> struct iterator_t: public std::iterator<std::random_access_iterator_tag, CT, int32_t> {
|
|
||||||
CT* _ptr;
|
|
||||||
iterator_t () noexcept: _ptr (nullptr) {}
|
|
||||||
iterator_t (CT* ptr) noexcept: _ptr (ptr) {}
|
|
||||||
iterator_t (const iterator_t<CT>& it) noexcept: _ptr (it._ptr) {}
|
|
||||||
|
|
||||||
CT& operator*() const noexcept {return *_ptr;}
|
|
||||||
CT* operator->() const noexcept {return _ptr;}
|
|
||||||
CT& operator[](int32_t ofs) const noexcept {return _ptr[ofs];}
|
|
||||||
|
|
||||||
iterator_t<CT>& operator++() noexcept {++_ptr; return *this;}
|
|
||||||
iterator_t<CT> operator++(int) noexcept {return iterator_t<CT> (_ptr++);};
|
|
||||||
iterator_t<CT>& operator--() noexcept {--_ptr; return *this;}
|
|
||||||
iterator_t<CT> operator--(int) noexcept {return iterator_t<CT> (_ptr--);};
|
|
||||||
bool operator == (const iterator_t<CT>& i2) const noexcept {return _ptr == i2._ptr;}
|
|
||||||
bool operator != (const iterator_t<CT>& i2) const noexcept {return _ptr != i2._ptr;}
|
|
||||||
bool operator < (const iterator_t<CT>& i2) const noexcept {return _ptr < i2._ptr;}
|
|
||||||
bool operator > (const iterator_t<CT>& i2) const noexcept {return _ptr > i2._ptr;}
|
|
||||||
bool operator <= (const iterator_t<CT>& i2) const noexcept {return _ptr <= i2._ptr;}
|
|
||||||
bool operator >= (const iterator_t<CT>& i2) const noexcept {return _ptr >= i2._ptr;}
|
|
||||||
iterator_t<CT> operator + (int32_t ofs) const noexcept {return iterator (_ptr + ofs);}
|
|
||||||
iterator_t<CT>& operator += (int32_t ofs) noexcept {_ptr += ofs; return *this;}
|
|
||||||
iterator_t<CT> operator - (int32_t ofs) const noexcept {return iterator (_ptr - ofs);}
|
|
||||||
iterator_t<CT>& operator -= (int32_t ofs) noexcept {_ptr -= ofs; return *this;}
|
|
||||||
};
|
|
||||||
// http://en.cppreference.com/w/cpp/concept/Container
|
|
||||||
typedef char value_type;
|
|
||||||
typedef char& reference;
|
|
||||||
typedef const char& const_reference;
|
|
||||||
typedef uint32_t size_type;
|
|
||||||
typedef int32_t difference_type;
|
|
||||||
typedef iterator_t<char> iterator;
|
|
||||||
typedef iterator_t<const char> const_iterator;
|
|
||||||
iterator begin() noexcept {return iterator ((char*) _buf);}
|
|
||||||
const_iterator begin() const noexcept {return const_iterator ((char*) _buf);}
|
|
||||||
iterator end() noexcept {return iterator ((char*) _buf + size());}
|
|
||||||
const_iterator end() const noexcept {return const_iterator ((char*) _buf + size());}
|
|
||||||
const_iterator cbegin() const noexcept {return const_iterator ((char*) _buf);}
|
|
||||||
const_iterator cend() const noexcept {return const_iterator ((char*) _buf + size());}
|
|
||||||
|
|
||||||
/** Returns -1 if not found. */
|
|
||||||
int32_t find (const char* str, int32_t pos, int32_t count) const noexcept {
|
|
||||||
const int32_t hlen = (int32_t) length() - pos;
|
|
||||||
if (hlen <= 0) return -1;
|
|
||||||
char* haystack = (char*) _buf + pos;
|
|
||||||
void* mret = memmem (haystack, hlen, str, count);
|
|
||||||
if (mret == 0) return -1;
|
|
||||||
return (char*) mret - (char*) _buf;
|
|
||||||
}
|
|
||||||
int32_t find (const char* str, int32_t pos = 0) const noexcept {return find (str, pos, strlen (str));}
|
|
||||||
|
|
||||||
/** Index of `ch` inside the string or -1 if not found. */
|
|
||||||
int32_t indexOf (char ch) const noexcept {
|
|
||||||
void* ret = memchr (_buf, ch, size());
|
|
||||||
return ret == nullptr ? -1 : (char*) ret - (char*) _buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helps to workaround the "statement has no effect" warning in `GSTRING_ON_STACK`.
|
|
||||||
gstring& self() noexcept {return *this;}
|
|
||||||
|
|
||||||
/** Grow buffer to be at least `to` characters long. */
|
|
||||||
void reserve (uint32_t to) {
|
|
||||||
uint32_t power = (_meta & CAPACITY_MASK) >> CAPACITY_OFFSET;
|
|
||||||
if (((uint32_t) 1 << power) < to) {
|
|
||||||
++power;
|
|
||||||
while (((uint32_t) 1 << power) < to) ++power;
|
|
||||||
if (power > 24) {GSTRING_ON_STACK (error, 64) << "gstring too large: " << (int) to; GTHROW (error.str());}
|
|
||||||
} else if (power) {
|
|
||||||
// No need to grow.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_meta = (_meta & ~CAPACITY_MASK) | (power << CAPACITY_OFFSET);
|
|
||||||
if (needsFreeing() && _buf != nullptr) {
|
|
||||||
_buf = ::realloc (_buf, capacity());
|
|
||||||
if (_buf == nullptr) GTHROW ("realloc failed");
|
|
||||||
} else {
|
|
||||||
const char* oldBuf = (const char*) _buf;
|
|
||||||
_buf = ::malloc (capacity());
|
|
||||||
if (_buf == nullptr) GTHROW ("malloc failed");
|
|
||||||
if (oldBuf != nullptr) ::memcpy (_buf, oldBuf, length());
|
|
||||||
_meta |= FREE_FLAG;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Length setter. Useful when you manually write into the buffer or to cut the string. */
|
|
||||||
void length (uint32_t len) noexcept {
|
|
||||||
_meta = (_meta & ~LENGTH_MASK) | (len & LENGTH_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
friend class gstring_stream;
|
|
||||||
public:
|
|
||||||
/** Appends an integer to the string.
|
|
||||||
* @param base Radix, from 1 to 36 (default 10).
|
|
||||||
* @param bytes How many bytes to reserve (24 by default). */
|
|
||||||
void append64 (int64_t iv, int base = 10, uint_fast8_t bytes = 24) {
|
|
||||||
uint32_t pos = length();
|
|
||||||
if (capacity() < pos + bytes) reserve (pos + bytes);
|
|
||||||
length (itoa ((char*) _buf + pos, iv, base) - (char*) _buf);
|
|
||||||
}
|
|
||||||
void append (char ch) {
|
|
||||||
uint32_t pos = length();
|
|
||||||
const uint32_t cap = capacity();
|
|
||||||
if (pos >= cap || cap <= 1) reserve (pos + 1);
|
|
||||||
((char*)_buf)[pos] = ch;
|
|
||||||
length (++pos);
|
|
||||||
}
|
|
||||||
void append (const char* cstr, uint32_t clen) {
|
|
||||||
uint32_t len = length();
|
|
||||||
uint32_t need = len + clen;
|
|
||||||
const uint32_t cap = capacity();
|
|
||||||
if (need > cap || cap <= 1) reserve (need);
|
|
||||||
::memcpy ((char*) _buf + len, cstr, clen);
|
|
||||||
length (need);
|
|
||||||
}
|
|
||||||
/** This one is for http://code.google.com/p/re2/; `clear` then `append`. */
|
|
||||||
bool ParseFrom (const char* cstr, int clen) {
|
|
||||||
if (clen < 0 || clen > (int) LENGTH_MASK) return false;
|
|
||||||
length (0); append (cstr, (uint32_t) clen); return true;}
|
|
||||||
gstring& operator << (const gstring& gs) {append (gs.data(), gs.length()); return *this;}
|
|
||||||
gstring& operator << (const std::string& str) {append (str.data(), str.length()); return *this;}
|
|
||||||
gstring& operator << (const char* cstr) {if (cstr) append (cstr, ::strlen (cstr)); return *this;}
|
|
||||||
gstring& operator << (char ch) {append (ch); return *this;}
|
|
||||||
gstring& operator << (int iv) {append64 (iv, 10, sizeof (int) * 3); return *this;}
|
|
||||||
gstring& operator << (long iv) {append64 (iv, 10, sizeof (long) * 3); return *this;}
|
|
||||||
gstring& operator << (long long iv) {append64 (iv, 10, sizeof (long long) * 3); return *this;}
|
|
||||||
gstring& operator << (double dv) {
|
|
||||||
uint32_t len = length();
|
|
||||||
reserve (len + 32);
|
|
||||||
int rc = snprintf (endp(), 31, "%f", dv);
|
|
||||||
if (rc > 0) {length (len + std::min (rc, 31));}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator < (const gstring &gs) const noexcept {
|
|
||||||
uint32_t len1 = length(); uint32_t len2 = gs.length();
|
|
||||||
if (len1 == len2) return ::strncmp (data(), gs.data(), len1) < 0;
|
|
||||||
int cmp = ::strncmp (data(), gs.data(), std::min (len1, len2));
|
|
||||||
if (cmp) return cmp < 0;
|
|
||||||
return len1 < len2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Asks `strftime` to generate a time string. Capacity is increased if necessary (up to a limit of +1024 bytes).
|
|
||||||
gstring& appendTime (const char* format, struct tm* tmv) {
|
|
||||||
int32_t pos = length(), cap = capacity(), left = cap - pos;
|
|
||||||
if (left < 8) {reserve (pos + 8); return appendTime (format, tmv);}
|
|
||||||
size_t got = strftime ((char*) _buf + pos, left, format, tmv);
|
|
||||||
if (got == 0) {
|
|
||||||
if (left > 1024) return *this; // Guard against perpetual growth.
|
|
||||||
reserve (pos + left * 2); return appendTime (format, tmv);
|
|
||||||
}
|
|
||||||
length (pos + got);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append the characters to this `gstring` wrapping them in the netstring format.
|
|
||||||
gstring& appendNetstring (const char* cstr, uint32_t clen) {
|
|
||||||
*this << (int) clen; append (':'); append (cstr, clen); append (','); return *this;}
|
|
||||||
/// Append the `gstr` wrapping it in the netstring format.
|
|
||||||
gstring& appendNetstring (const gstring& gstr) {return appendNetstring (gstr.data(), gstr.length());}
|
|
||||||
|
|
||||||
std::ostream& writeAsNetstring (std::ostream& stream) const;
|
|
||||||
|
|
||||||
/// Parse netstring at `pos` and return a `gstring` *pointing* at the parsed netstring.\n
|
|
||||||
/// No heap space allocated.\n
|
|
||||||
/// Throws std::runtime_error if netstring parsing fails.\n
|
|
||||||
/// If parsing was successfull, then `after` is set to point after the parsed netstring.
|
|
||||||
gstring netstringAt (uint32_t pos, uint32_t* after = nullptr) const {
|
|
||||||
const uint32_t len = length(); char* buf = (char*) _buf;
|
|
||||||
if (buf == nullptr) GTHROW ("gstring: netstringAt: nullptr");
|
|
||||||
uint32_t next = pos;
|
|
||||||
while (next < len && buf[next] >= '0' && buf[next] <= '9') ++next;
|
|
||||||
if (next >= len || buf[next] != ':' || next - pos > 10) GTHROW ("gstring: netstringAt: no header");
|
|
||||||
char* endptr = 0;
|
|
||||||
long nlen = ::strtol (buf + pos, &endptr, 10);
|
|
||||||
if (endptr != buf + next) GTHROW ("gstring: netstringAt: unexpected header end");
|
|
||||||
pos = next + 1; next = pos + nlen;
|
|
||||||
if (next >= len || buf[next] != ',') GTHROW ("gstring: netstringAt: no body");
|
|
||||||
if (after) *after = next + 1;
|
|
||||||
return gstring (0, buf + pos, false, next - pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper around strtol, not entirely safe (make sure the string is terminated with a non-digit, by calling c_str, for example).
|
|
||||||
long intAt (uint32_t pos, uint32_t* after = nullptr, int base = 10) const {
|
|
||||||
// BTW: http://www.kumobius.com/2013/08/c-string-to-int/
|
|
||||||
const uint32_t len = length(); char* buf = (char*) _buf;
|
|
||||||
if (pos >= len || buf == nullptr) GTHROW ("gstring: intAt: pos >= len");
|
|
||||||
char* endptr = 0;
|
|
||||||
long lv = ::strtol (buf + pos, &endptr, base);
|
|
||||||
uint32_t next = endptr - buf;
|
|
||||||
if (next > len) GTHROW ("gstring: intAt: endptr > len");
|
|
||||||
if (after) *after = next;
|
|
||||||
return lv;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper around strtol. Copies the string into a temporary buffer in order to pass it to strtol. Empty string returns 0.
|
|
||||||
long toInt (int base = 10) const noexcept {
|
|
||||||
const uint32_t len = length(); if (len == 0) return 0;
|
|
||||||
char buf[len + 1]; memcpy (buf, _buf, len); buf[len] = 0;
|
|
||||||
return ::strtol (buf, nullptr, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a single netstring from the `stream` and append it to the end of `gstring`.
|
|
||||||
/// Throws an exception if the input is not a well-formed netstring.
|
|
||||||
gstring& readNetstring (std::istream& stream) {
|
|
||||||
int32_t nlen; stream >> nlen;
|
|
||||||
if (!stream.good() || nlen < 0) GTHROW ("!netstring");
|
|
||||||
int ch = stream.get();
|
|
||||||
if (!stream.good() || ch != ':') GTHROW ("!netstring");
|
|
||||||
uint32_t glen = length();
|
|
||||||
const uint32_t cap = capacity();
|
|
||||||
if (cap < glen + nlen || cap <= 1) reserve (glen + nlen);
|
|
||||||
stream.read ((char*) _buf + glen, nlen);
|
|
||||||
if (!stream.good()) GTHROW ("!netstring");
|
|
||||||
ch = stream.get();
|
|
||||||
if (ch != ',') GTHROW ("!netstring");
|
|
||||||
length (glen + nlen);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set length to 0. `_buf` not changed.
|
|
||||||
gstring& clear() noexcept {length (0); return *this;}
|
|
||||||
|
|
||||||
/// Removes `count` characters starting at `pos`.
|
|
||||||
gstring& erase (uint32_t pos, uint32_t count = 1) noexcept {
|
|
||||||
const char* buf = (const char*) _buf;
|
|
||||||
const char* pt1 = buf + pos;
|
|
||||||
const char* pt2 = pt1 + count;
|
|
||||||
uint32_t len = length();
|
|
||||||
const char* end = buf + len;
|
|
||||||
if (pt2 <= end) {
|
|
||||||
length (len - count);
|
|
||||||
::memmove ((void*) pt1, (void*) pt2, end - pt2);
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
/// Remove characters [from,till) and return `from`.\n
|
|
||||||
/// Compatible with "boost/algorithm/string/trim.hpp".
|
|
||||||
iterator_t<char> erase (iterator_t<char> from, iterator_t<char> till) noexcept {
|
|
||||||
intptr_t ipos = from._ptr - (char*) _buf;
|
|
||||||
intptr_t count = till._ptr - from._ptr;
|
|
||||||
if (ipos >= 0 && count > 0) erase (ipos, count);
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
|
|
||||||
~gstring() noexcept {
|
|
||||||
if (_buf != nullptr && needsFreeing()) {::free (_buf); _buf = nullptr;}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline bool operator == (const gstring& gs1, const gstring& gs2) noexcept {return gs1.equals (gs2);}
|
|
||||||
inline bool operator == (const char* cstr, const gstring& gstr) noexcept {return gstr.equals (cstr);}
|
|
||||||
inline bool operator == (const gstring& gstr, const char* cstr) noexcept {return gstr.equals (cstr);}
|
|
||||||
inline bool operator != (const gstring& gs1, const gstring& gs2) noexcept {return !gs1.equals (gs2);}
|
|
||||||
inline bool operator != (const char* cstr, const gstring& gstr) noexcept {return !gstr.equals (cstr);}
|
|
||||||
inline bool operator != (const gstring& gstr, const char* cstr) noexcept {return !gstr.equals (cstr);}
|
|
||||||
|
|
||||||
inline bool operator == (const gstring& gstr, const std::string& str) noexcept {
|
|
||||||
return gstr.equals (gstring (gstring::ReferenceConstructor(), str.data(), str.size()));}
|
|
||||||
inline bool operator != (const gstring& gstr, const std::string& str) noexcept {return !(gstr == str);}
|
|
||||||
inline bool operator == (const std::string& str, const gstring& gstr) noexcept {return gstr == str;}
|
|
||||||
inline bool operator != (const std::string& str, const gstring& gstr) noexcept {return !(gstr == str);}
|
|
||||||
inline std::string operator += (std::string& str, const gstring& gstr) {return str.append (gstr.data(), gstr.size());}
|
|
||||||
inline std::string operator + (const std::string& str, const gstring& gstr) {return std::string (str) .append (gstr.data(), gstr.size());}
|
|
||||||
|
|
||||||
inline std::ostream& operator << (std::ostream& os, const gstring& gstr) {
|
|
||||||
if (gstr._buf != nullptr) os.write ((const char*) gstr._buf, gstr.length());
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encode this `gstring` into `stream` as a netstring.
|
|
||||||
inline std::ostream& gstring::writeAsNetstring (std::ostream& stream) const {
|
|
||||||
stream << length() << ':' << *this << ',';
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.mr-edd.co.uk/blog/beginners_guide_streambuf
|
|
||||||
// http://www.dreamincode.net/code/snippet2499.htm
|
|
||||||
// http://spec.winprog.org/streams/
|
|
||||||
class gstring_stream: public std::basic_streambuf<char, std::char_traits<char> > {
|
|
||||||
gstring& _gstr;
|
|
||||||
public:
|
|
||||||
gstring_stream (gstring& gstr) noexcept: _gstr (gstr) {
|
|
||||||
char* buf = (char*) gstr._buf;
|
|
||||||
if (buf != nullptr) setg (buf, buf, buf + gstr.length());
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
virtual int_type overflow (int_type ch) {
|
|
||||||
if (__builtin_expect (ch != traits_type::eof(), 1)) _gstr.append ((char) ch);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// no copying
|
|
||||||
gstring_stream (const gstring_stream &);
|
|
||||||
gstring_stream& operator = (const gstring_stream &);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace glim
|
|
||||||
|
|
||||||
// hash specialization
|
|
||||||
// cf. http://stackoverflow.com/questions/8157937/how-to-specialize-stdhashkeyoperator-for-user-defined-type-in-unordered
|
|
||||||
namespace std {
|
|
||||||
template <> struct hash<glim::gstring> {
|
|
||||||
size_t operator()(const glim::gstring& gs) const noexcept {
|
|
||||||
// cf. http://stackoverflow.com/questions/7666509/hash-function-for-string
|
|
||||||
// Would be nice to use https://131002.net/siphash/ here.
|
|
||||||
uint32_t hash = 5381;
|
|
||||||
uint32_t len = gs.length();
|
|
||||||
if (len) {
|
|
||||||
const char* str = (const char*) gs._buf;
|
|
||||||
const char* end = str + len;
|
|
||||||
while (str < end) hash = ((hash << 5) + hash) + *str++; /* hash * 33 + c */
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // _GSTRING_INCLUDED
|
|
255
external/glim/hget.hpp
vendored
255
external/glim/hget.hpp
vendored
|
@ -1,255 +0,0 @@
|
||||||
// Simple header-only wrapper around libevent's evhttp client.
|
|
||||||
// See also: https://github.com/cpp-netlib/cpp-netlib/issues/160
|
|
||||||
|
|
||||||
#ifndef _GLIM_HGET_INCLUDED
|
|
||||||
#define _GLIM_HGET_INCLUDED
|
|
||||||
|
|
||||||
#include <event2/event.h>
|
|
||||||
#include <event2/dns.h>
|
|
||||||
#include <evhttp.h> // http://stackoverflow.com/a/5237994; http://archives.seul.org/libevent/users/Sep-2010/msg00050.html
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <functional>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <iostream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
#include "exception.hpp"
|
|
||||||
#include "gstring.hpp"
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
/// HTTP results
|
|
||||||
struct hgot {
|
|
||||||
int32_t status = 0;
|
|
||||||
/// Uses errno codes.
|
|
||||||
int32_t error = 0;
|
|
||||||
struct evbuffer* body = 0;
|
|
||||||
struct evhttp_request* req = 0;
|
|
||||||
size_t bodyLength() const {return body ? evbuffer_get_length (body) : 0;}
|
|
||||||
/// Warning: the string is NOT zero-terminated.
|
|
||||||
const char* bodyData() {return body ? (const char*) evbuffer_pullup (body, -1) : "";}
|
|
||||||
/// Returns a zero-terminated string. Warning: modifies the `body` every time in order to add the terminator.
|
|
||||||
const char* cbody() {if (!body) return ""; evbuffer_add (body, "", 1); return (const char*) evbuffer_pullup (body, -1);}
|
|
||||||
/// A gstring *view* into the `body`.
|
|
||||||
glim::gstring gbody() {
|
|
||||||
if (!body) return glim::gstring();
|
|
||||||
return glim::gstring (glim::gstring::ReferenceConstructor(), (const char*) evbuffer_pullup (body, -1), evbuffer_get_length (body));}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Used internally to pass both connection and handler into callback.
|
|
||||||
struct hgetContext {
|
|
||||||
struct evhttp_connection* conn;
|
|
||||||
std::function<void(hgot&)> handler;
|
|
||||||
hgetContext (struct evhttp_connection* conn, std::function<void(hgot&)> handler): conn (conn), handler (handler) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Invoked when evhttp finishes a request.
|
|
||||||
inline void hgetCB (struct evhttp_request* req, void* ctx_){
|
|
||||||
hgetContext* ctx = (hgetContext*) ctx_;
|
|
||||||
|
|
||||||
hgot gt;
|
|
||||||
if (req == NULL) gt.error = ETIMEDOUT;
|
|
||||||
else if (req->response_code == 0) gt.error = ECONNREFUSED;
|
|
||||||
else {
|
|
||||||
gt.status = req->response_code;
|
|
||||||
gt.body = req->input_buffer;
|
|
||||||
gt.req = req;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ctx->handler (gt);
|
|
||||||
} catch (const std::runtime_error& ex) { // Shouldn't normally happen:
|
|
||||||
std::cerr << "glim::hget, handler exception: " << ex.what() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
evhttp_connection_free ((struct evhttp_connection*) ctx->conn);
|
|
||||||
//freed by libevent//if (req != NULL) evhttp_request_free (req);
|
|
||||||
delete ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
C++ wrapper around libevent's http client.
|
|
||||||
Example: \code
|
|
||||||
hget (evbase, dnsbase) .setRequestBuilder ([](struct evhttp_request* req){
|
|
||||||
evbuffer_add (req->output_buffer, "foo", 3);
|
|
||||||
evhttp_add_header (req->output_headers, "Content-Length", "3");
|
|
||||||
}) .go ("http://127.0.0.1:8080/test", [](hgot& got){
|
|
||||||
if (got.error) log_warn ("127.0.0.1:8080 " << strerror (got.error));
|
|
||||||
else if (got.status != 200) log_warn ("127.0.0.1:8080 != 200");
|
|
||||||
else log_info ("got " << evbuffer_get_length (got.body) << " bytes from /test: " << evbuffer_pullup (got.body, -1));
|
|
||||||
}); \endcode
|
|
||||||
*/
|
|
||||||
class hget {
|
|
||||||
public:
|
|
||||||
std::shared_ptr<struct event_base> _evbase;
|
|
||||||
std::shared_ptr<struct evdns_base> _dnsbase;
|
|
||||||
std::function<void(struct evhttp_request*)> _requestBuilder;
|
|
||||||
enum evhttp_cmd_type _method;
|
|
||||||
public:
|
|
||||||
typedef std::shared_ptr<struct evhttp_uri> uri_t;
|
|
||||||
/// The third parameter is the request number, starting from 1.
|
|
||||||
typedef std::function<float(hgot&,uri_t,int32_t)> until_handler_t;
|
|
||||||
public:
|
|
||||||
hget (std::shared_ptr<struct event_base> evbase, std::shared_ptr<struct evdns_base> dnsbase):
|
|
||||||
_evbase (evbase), _dnsbase (dnsbase), _method (EVHTTP_REQ_GET) {}
|
|
||||||
|
|
||||||
/// Modifies the request before its execution.
|
|
||||||
hget& setRequestBuilder (std::function<void(struct evhttp_request*)> rb) {
|
|
||||||
_requestBuilder = rb;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Uses a simple request builder to send the `str`.
|
|
||||||
* `str` is a `char` string class with methods `data` and `size`. */
|
|
||||||
template<typename STR> hget& payload (STR str, const char* contentType = nullptr, enum evhttp_cmd_type method = EVHTTP_REQ_POST) {
|
|
||||||
_method = method;
|
|
||||||
return setRequestBuilder ([str,contentType](struct evhttp_request* req) {
|
|
||||||
if (contentType) evhttp_add_header (req->output_headers, "Content-Type", contentType);
|
|
||||||
char buf[64];
|
|
||||||
*glim::itoa (buf, (int) str.size()) = 0;
|
|
||||||
evhttp_add_header (req->output_headers, "Content-Length", buf);
|
|
||||||
evbuffer_add (req->output_buffer, (const void*) str.data(), (size_t) str.size());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
struct evhttp_request* go (uri_t uri, int32_t timeoutSec, std::function<void(hgot&)> handler) {
|
|
||||||
int port = evhttp_uri_get_port (uri.get());
|
|
||||||
if (port == -1) port = 80;
|
|
||||||
struct evhttp_connection* conn = evhttp_connection_base_new (_evbase.get(), _dnsbase.get(),
|
|
||||||
evhttp_uri_get_host (uri.get()), port);
|
|
||||||
evhttp_connection_set_timeout (conn, timeoutSec);
|
|
||||||
struct evhttp_request *req = evhttp_request_new (hgetCB, new hgetContext(conn, handler));
|
|
||||||
int ret = evhttp_add_header (req->output_headers, "Host", evhttp_uri_get_host (uri.get()));
|
|
||||||
if (ret) throw std::runtime_error ("hget: evhttp_add_header(Host) != 0");
|
|
||||||
if (_requestBuilder) _requestBuilder (req);
|
|
||||||
const char* get = evhttp_uri_get_path (uri.get());
|
|
||||||
const char* qs = evhttp_uri_get_query (uri.get());
|
|
||||||
if (qs == NULL) {
|
|
||||||
ret = evhttp_make_request (conn, req, _method, get);
|
|
||||||
} else {
|
|
||||||
size_t getLen = strlen (get);
|
|
||||||
size_t qsLen = strlen (qs);
|
|
||||||
char buf[getLen + 1 + qsLen + 1];
|
|
||||||
char* caret = stpcpy (buf, get);
|
|
||||||
*caret++ = '?';
|
|
||||||
caret = stpcpy (caret, qs);
|
|
||||||
assert (caret - buf < sizeof (buf));
|
|
||||||
ret = evhttp_make_request (conn, req, _method, buf);
|
|
||||||
}
|
|
||||||
if (ret) throw std::runtime_error ("hget: evhttp_make_request != 0");
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
struct evhttp_request* go (const char* url, int32_t timeoutSec, std::function<void(hgot&)> handler) {
|
|
||||||
return go (std::shared_ptr<struct evhttp_uri> (evhttp_uri_parse (url), evhttp_uri_free), timeoutSec, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
void goUntil (std::vector<uri_t> urls, until_handler_t handler, int32_t timeoutSec = 20);
|
|
||||||
/**
|
|
||||||
Parse urls and call `goUntil`.
|
|
||||||
Example (trying ten times to reach the servers): \code
|
|
||||||
std::string path ("/path");
|
|
||||||
hget.goUntilS (boost::assign::list_of ("http://server1" + path) ("http://server2" + path),
|
|
||||||
[](hgot& got, hget::uri_t uri, int32_t num)->float {
|
|
||||||
std::cout << "server: " << evhttp_uri_get_host (uri.get()) << "; request number: " << num << std::endl;
|
|
||||||
if (got.status != 200 && num < 10) return 1.f; // Retry in a second.
|
|
||||||
return -1.f; // No need to retry the request.
|
|
||||||
});
|
|
||||||
\endcode
|
|
||||||
@param urls is a for-compatible container of strings (where string has methods `data` and `size`).
|
|
||||||
*/
|
|
||||||
template<typename URLS> void goUntilS (URLS&& urls, until_handler_t handler, int32_t timeoutSec = 20) {
|
|
||||||
std::vector<uri_t> parsedUrls;
|
|
||||||
for (auto&& url: urls) {
|
|
||||||
// Copying to stack might be cheaper than malloc in c_str.
|
|
||||||
int len = url.size(); char buf[len + 1]; memcpy (buf, url.data(), len); buf[len] = 0;
|
|
||||||
struct evhttp_uri* uri = evhttp_uri_parse (buf);
|
|
||||||
if (!uri) GTHROW (std::string ("!evhttp_uri_parse: ") + buf);
|
|
||||||
parsedUrls.push_back (uri_t (uri, evhttp_uri_free));
|
|
||||||
}
|
|
||||||
goUntil (parsedUrls, handler, timeoutSec);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
Parse urls and call `goUntil`.
|
|
||||||
Example (trying ten times to reach the servers): \code
|
|
||||||
hget.goUntilC (boost::assign::list_of ("http://server1/") ("http://server2/"),
|
|
||||||
[](hgot& got, hget::uri_t uri, int32_t num)->float {
|
|
||||||
std::cout << "server: " << evhttp_uri_get_host (uri.get()) << "; request number: " << num << std::endl;
|
|
||||||
if (got.status != 200 && num < 10) return 1.f; // Retry in a second.
|
|
||||||
return -1.f; // No need to retry the request.
|
|
||||||
});
|
|
||||||
\endcode
|
|
||||||
Or with `std::array` instead of `boost::assign::list_of`: \code
|
|
||||||
std::array<const char*, 2> urls {{"http://server1/", "http://server2/"}};
|
|
||||||
hget.goUntilC (urls, [](hgot& got, hget::uri_t uri, int32_t num)->float {
|
|
||||||
return got.status != 200 && num < 10 ? 0.f : -1.f;});
|
|
||||||
\endcode
|
|
||||||
@param urls is a for-compatible container of C strings (const char*).
|
|
||||||
*/
|
|
||||||
template<typename URLS> void goUntilC (URLS&& urls, until_handler_t handler, int32_t timeoutSec = 20) {
|
|
||||||
std::vector<uri_t> parsedUrls;
|
|
||||||
for (auto url: urls) {
|
|
||||||
struct evhttp_uri* uri = evhttp_uri_parse (url);
|
|
||||||
if (!uri) GTHROW (std::string ("Can't parse url: ") + url);
|
|
||||||
parsedUrls.push_back (uri_t (uri, evhttp_uri_free));
|
|
||||||
}
|
|
||||||
goUntil (parsedUrls, handler, timeoutSec);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void hgetUntilRetryCB (evutil_socket_t, short, void* utilHandlerPtr); // event_callback_fn
|
|
||||||
|
|
||||||
/** `hget::goUntil` implementation.
|
|
||||||
* This function object is passed to `hget::go` as a handler and calls `hget::go` again if necessary. */
|
|
||||||
struct HgetUntilHandler {
|
|
||||||
hget _hget;
|
|
||||||
hget::until_handler_t _handler;
|
|
||||||
std::vector<hget::uri_t> _urls;
|
|
||||||
int32_t _timeoutSec;
|
|
||||||
int32_t _requestNum;
|
|
||||||
uint8_t _nextUrl; ///< A round-robin pointer to the next url in `_urls`.
|
|
||||||
HgetUntilHandler (hget& hg, hget::until_handler_t handler, std::vector<hget::uri_t> urls, int32_t timeoutSec):
|
|
||||||
_hget (hg), _handler (handler), _urls (urls), _timeoutSec (timeoutSec), _requestNum (0), _nextUrl (0) {}
|
|
||||||
void operator() (hgot& got) {
|
|
||||||
uint8_t urlNum = _nextUrl ? _nextUrl - 1 : _urls.size() - 1;
|
|
||||||
float retryAfterSec = _handler (got, _urls[urlNum], _requestNum);
|
|
||||||
if (retryAfterSec == 0.f) retry();
|
|
||||||
else if (retryAfterSec > 0.f) {
|
|
||||||
struct timeval wait;
|
|
||||||
wait.tv_sec = (int) retryAfterSec;
|
|
||||||
retryAfterSec -= wait.tv_sec;
|
|
||||||
wait.tv_usec = (int) (retryAfterSec * 1000000.f);
|
|
||||||
int rc = event_base_once (_hget._evbase.get(), -1, EV_TIMEOUT, hgetUntilRetryCB, new HgetUntilHandler (*this), &wait);
|
|
||||||
if (rc) throw std::runtime_error ("HgetUntilHandler: event_base_once != 0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void start() {retry();}
|
|
||||||
void retry() {
|
|
||||||
uint8_t nextUrl = _nextUrl++;
|
|
||||||
if (_nextUrl >= _urls.size()) _nextUrl = 0;
|
|
||||||
++_requestNum;
|
|
||||||
_hget.go (_urls[nextUrl], _timeoutSec, *this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Used in `hget::goUntil` to wait in `evtimer_new` before repeating the request.
|
|
||||||
inline void hgetUntilRetryCB (evutil_socket_t, short, void* utilHandlerPtr) { // event_callback_fn
|
|
||||||
std::unique_ptr<HgetUntilHandler> untilHandler ((HgetUntilHandler*) utilHandlerPtr);
|
|
||||||
untilHandler->retry();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows to retry the request using multiple URLs in a round-robin fashion.
|
|
||||||
* The `handler` returns the number of seconds to wait before retrying the request or -1 if no retry is necessary.
|
|
||||||
*/
|
|
||||||
inline void hget::goUntil (std::vector<uri_t> urls, until_handler_t handler, int32_t timeoutSec) {
|
|
||||||
HgetUntilHandler (*this, handler, urls, timeoutSec) .start();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // _GLIM_HGET_INCLUDED
|
|
384
external/glim/ldb.hpp
vendored
384
external/glim/ldb.hpp
vendored
|
@ -1,384 +0,0 @@
|
||||||
#ifndef _GLIM_LDB_HPP_INCLUDED
|
|
||||||
#define _GLIM_LDB_HPP_INCLUDED
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Leveldb (http://code.google.com/p/leveldb/) wrapper.
|
|
||||||
* @code
|
|
||||||
Copyright 2012 Kozarezov Artem Aleksandrovich
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
* @endcode
|
|
||||||
* @file
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
//#include <unordered_map> // having SIGFPE as in http://stackoverflow.com/q/13580823/257568
|
|
||||||
#include <map>
|
|
||||||
#include <climits> // CHAR_MAX
|
|
||||||
|
|
||||||
#include <leveldb/db.h>
|
|
||||||
#include <leveldb/write_batch.h>
|
|
||||||
#include <leveldb/filter_policy.h>
|
|
||||||
#include <boost/archive/binary_oarchive.hpp>
|
|
||||||
#include <boost/archive/binary_iarchive.hpp>
|
|
||||||
#include <boost/serialization/serialization.hpp>
|
|
||||||
#include <boost/noncopyable.hpp>
|
|
||||||
#include <boost/iterator/iterator_facade.hpp>
|
|
||||||
#include <boost/range/iterator_range.hpp> // http://www.boost.org/doc/libs/1_52_0/libs/range/doc/html/range/reference/utilities/iterator_range.html
|
|
||||||
|
|
||||||
#include <arpa/inet.h> // htonl, ntohl
|
|
||||||
#include <sys/stat.h> // mkdir
|
|
||||||
#include <sys/types.h> // mkdir
|
|
||||||
#include <string.h> // strerror
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "gstring.hpp"
|
|
||||||
#include "exception.hpp"
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
G_DEFINE_EXCEPTION (LdbEx);
|
|
||||||
|
|
||||||
template <typename T> inline void ldbSerialize (gstring& bytes, const T& data) {
|
|
||||||
gstring_stream stream (bytes);
|
|
||||||
boost::archive::binary_oarchive oa (stream, boost::archive::no_header);
|
|
||||||
oa << data;
|
|
||||||
}
|
|
||||||
template <typename V> inline void ldbDeserialize (const gstring& bytes, V& data) {
|
|
||||||
gstring_stream stream (const_cast<gstring&> (bytes));
|
|
||||||
boost::archive::binary_iarchive ia (stream, boost::archive::no_header);
|
|
||||||
ia >> data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** uint32_t keys are stored big-endian (network byte order) in order to be compatible with lexicographic ordering. */
|
|
||||||
template <> inline void ldbSerialize<uint32_t> (gstring& bytes, const uint32_t& ui) {
|
|
||||||
uint32_t nui = htonl (ui); bytes.append ((const char*) &nui, sizeof (uint32_t));}
|
|
||||||
/** Deserialize uint32_t from big-endian (network byte order). */
|
|
||||||
template <> inline void ldbDeserialize<uint32_t> (const gstring& bytes, uint32_t& ui) {
|
|
||||||
if (bytes.size() != sizeof (uint32_t)) GNTHROW (LdbEx, "Not uint32_t, wrong number of bytes");
|
|
||||||
uint32_t nui = * (uint32_t*) bytes.data(); ui = ntohl (nui);}
|
|
||||||
|
|
||||||
/** If the data is `gstring` then use the data's buffer directly, no copy. */
|
|
||||||
template <> inline void ldbSerialize<gstring> (gstring& bytes, const gstring& data) {
|
|
||||||
bytes = gstring (0, (void*) data.data(), false, data.length());}
|
|
||||||
/** Deserializing into `gstring` copies the bytes into it, reusing its buffer. */
|
|
||||||
template <> inline void ldbDeserialize<gstring> (const gstring& bytes, gstring& data) {
|
|
||||||
data.clear() << bytes;}
|
|
||||||
|
|
||||||
/** If the data is `std::string` then use the data's buffer directly, no copy. */
|
|
||||||
template <> inline void ldbSerialize<std::string> (gstring& bytes, const std::string& data) {
|
|
||||||
bytes = gstring (0, (void*) data.data(), false, data.length());}
|
|
||||||
/** Deserializing into `std::string` copies the bytes into it, reusing its buffer. */
|
|
||||||
template <> inline void ldbDeserialize<std::string> (const gstring& bytes, std::string& data) {
|
|
||||||
data.clear(); data.append (bytes.data(), bytes.size());}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Header-only Leveldb wrapper.\n
|
|
||||||
* Uses Boost Serialization to pack keys and values (glim::gstring can be used for raw bytes).\n
|
|
||||||
* Allows semi-automatic indexing with triggers.
|
|
||||||
*/
|
|
||||||
struct Ldb {
|
|
||||||
std::shared_ptr<leveldb::DB> _db;
|
|
||||||
std::shared_ptr<const leveldb::FilterPolicy> _filter;
|
|
||||||
|
|
||||||
struct IteratorEntry { ///< Something to be `dereference`d from the Iterator. Also a pImpl allowing to keep the `_valid` and the `_lit` in sync.
|
|
||||||
leveldb::Iterator* _lit;
|
|
||||||
bool _valid:1;
|
|
||||||
|
|
||||||
IteratorEntry (const IteratorEntry&) = delete; // Owns `leveldb::Iterator`, should not be copied.
|
|
||||||
IteratorEntry (IteratorEntry&&) = default;
|
|
||||||
|
|
||||||
IteratorEntry (leveldb::Iterator* lit, bool valid = false): _lit (lit), _valid (valid) {}
|
|
||||||
~IteratorEntry() {delete _lit;}
|
|
||||||
|
|
||||||
/** Zero-copy view of the current key bytes. Should *not* be used after the Iterator is changed or destroyed.
|
|
||||||
* @param ref If true then the *copies* of the returned gstring will keep pointing to LevelDB memory which is valid only until the iterator changes. */
|
|
||||||
const gstring keyView (bool ref = false) const {
|
|
||||||
if (!_valid) return gstring();
|
|
||||||
const leveldb::Slice& key = _lit->key();
|
|
||||||
return gstring (0, (void*) key.data(), false, key.size(), ref);} // Zero copy.
|
|
||||||
/** Zero-copy view of the current value bytes. Should *not* be used after the Iterator is changed or destroyed.
|
|
||||||
* @param ref If true then the *copies* of the returned gstring will keep pointing to LevelDB memory which is valid only until the iterator changes. */
|
|
||||||
const gstring valueView (bool ref = false) const {
|
|
||||||
if (!_valid) return gstring();
|
|
||||||
const leveldb::Slice& val = _lit->value();
|
|
||||||
return gstring (0, (void*) val.data(), false, val.size(), ref);} // Zero copy.
|
|
||||||
/** Deserialize into `key`. */
|
|
||||||
template <typename T> void getKey (T& key) const {ldbDeserialize (keyView (true), key);}
|
|
||||||
/** Deserialize the key into a temporary and return it. */
|
|
||||||
template <typename T> T getKey() const {T key; getKey (key); return key;}
|
|
||||||
/** Deserialize into `value`. */
|
|
||||||
template <typename T> void getValue (T& value) const {ldbDeserialize (valueView (true), value);}
|
|
||||||
/** Deserialize the value into a temporary and return it. */
|
|
||||||
template <typename T> T getValue() const {T value; getValue (value); return value;}
|
|
||||||
};
|
|
||||||
struct NoSeekFlag {}; ///< Tells the `Iterator` constructor not to seek to the beginning of the database.
|
|
||||||
/** Wraps Leveldb iterator.
|
|
||||||
* Note: "In fact the iterator is a light-weight snapshot. It will see exactly the version of the DB that existed when the iterator was created
|
|
||||||
* (i.e., any insert/delete done after the iterator is created, regardless of which thread does them) will be invisible to the iterator"
|
|
||||||
* (https://groups.google.com/d/msg/leveldb/nX8S5KKiSn4/PI92Yf1Hf6UJ). */
|
|
||||||
struct Iterator: public boost::iterator_facade<Iterator, IteratorEntry, boost::bidirectional_traversal_tag> {
|
|
||||||
std::shared_ptr<IteratorEntry> _entry; ///< The Iterator might be copied around, therefore we keep the real iterator and the state in the shared_ptr.
|
|
||||||
|
|
||||||
Iterator (const Iterator&) = default;
|
|
||||||
Iterator (Iterator&&) = default;
|
|
||||||
Iterator& operator= (const Iterator&) = default;
|
|
||||||
Iterator& operator= (Iterator&&) = default;
|
|
||||||
/** Iterate from the beginning or the end of the database.
|
|
||||||
* @param position can be MDB_FIRST or MDB_LAST */
|
|
||||||
Iterator (Ldb* ldb, leveldb::ReadOptions options = leveldb::ReadOptions()):
|
|
||||||
_entry (std::make_shared<IteratorEntry> (ldb->_db->NewIterator (options))) {
|
|
||||||
IteratorEntry* entry = _entry.get();
|
|
||||||
entry->_lit->SeekToFirst();
|
|
||||||
entry->_valid = entry->_lit->Valid();
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator (Ldb* ldb, NoSeekFlag, leveldb::ReadOptions options = leveldb::ReadOptions()):
|
|
||||||
_entry (std::make_shared<IteratorEntry> (ldb->_db->NewIterator (options))) {}
|
|
||||||
/** True if the iterator isn't pointing anywhere. */
|
|
||||||
bool end() const {return !_entry->_valid;}
|
|
||||||
|
|
||||||
bool equal (const Iterator& other) const {
|
|
||||||
bool weAreValid = _entry->_valid, theyAreValid = other._entry->_valid;
|
|
||||||
if (!weAreValid) return !theyAreValid;
|
|
||||||
if (!theyAreValid) return false;
|
|
||||||
auto&& ourKey = _entry->_lit->key(), theirKey = other._entry->_lit->key();
|
|
||||||
if (ourKey.size() != theirKey.size()) return false;
|
|
||||||
return memcmp (ourKey.data(), theirKey.data(), ourKey.size()) == 0;
|
|
||||||
}
|
|
||||||
IteratorEntry& dereference() const {
|
|
||||||
// NB: Boost iterator_facade expects the `dereference` to be a `const` method.
|
|
||||||
// I guess Iterator is not modified, so the `dereference` is `const`, even though the Entry can be modified.
|
|
||||||
return *_entry;
|
|
||||||
}
|
|
||||||
virtual void increment() {
|
|
||||||
IteratorEntry* entry = _entry.get();
|
|
||||||
if (entry->_valid) entry->_lit->Next();
|
|
||||||
else entry->_lit->SeekToFirst();
|
|
||||||
entry->_valid = entry->_lit->Valid();
|
|
||||||
}
|
|
||||||
virtual void decrement() {
|
|
||||||
IteratorEntry* entry = _entry.get();
|
|
||||||
if (entry->_valid) entry->_lit->Prev();
|
|
||||||
else entry->_lit->SeekToLast();
|
|
||||||
entry->_valid = entry->_lit->Valid();
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator& seek (const gstring& key) {
|
|
||||||
IteratorEntry* entry = _entry.get();
|
|
||||||
entry->_lit->Seek (leveldb::Slice (key.data(), key.size()));
|
|
||||||
entry->_valid = entry->_lit->Valid();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Iterator begin() {return Iterator (this);}
|
|
||||||
Iterator end() {return Iterator (this, NoSeekFlag());}
|
|
||||||
|
|
||||||
/** Range from `from` (inclusive) to `till` (exclusive). */
|
|
||||||
template <typename K>
|
|
||||||
boost::iterator_range<Iterator> range (const K& from, const K& till, leveldb::ReadOptions options = leveldb::ReadOptions()) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
ldbSerialize (kbytes, from);
|
|
||||||
Iterator fit (this, NoSeekFlag(), options); fit.seek (kbytes);
|
|
||||||
|
|
||||||
ldbSerialize (kbytes.clear(), till);
|
|
||||||
Iterator tit (this, NoSeekFlag(), options); tit.seek (kbytes);
|
|
||||||
|
|
||||||
return boost::iterator_range<Iterator> (fit, tit);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StartsWithIterator: public Iterator {
|
|
||||||
gstring _starts;
|
|
||||||
StartsWithIterator (const StartsWithIterator&) = default;
|
|
||||||
StartsWithIterator (StartsWithIterator&&) = default;
|
|
||||||
StartsWithIterator& operator= (const StartsWithIterator&) = default;
|
|
||||||
StartsWithIterator& operator= (StartsWithIterator&&) = default;
|
|
||||||
|
|
||||||
StartsWithIterator (Ldb* ldb, const char* data, uint32_t length, leveldb::ReadOptions options = leveldb::ReadOptions()):
|
|
||||||
Iterator (ldb, NoSeekFlag(), options), _starts (data, length) {
|
|
||||||
IteratorEntry* entry = _entry.get();
|
|
||||||
entry->_lit->Seek (leveldb::Slice (data, length));
|
|
||||||
entry->_valid = checkValidity();
|
|
||||||
}
|
|
||||||
/** End iterator, pointing nowhere. */
|
|
||||||
StartsWithIterator (Ldb* ldb, const char* data, uint32_t length, NoSeekFlag, leveldb::ReadOptions options = leveldb::ReadOptions()):
|
|
||||||
Iterator (ldb, NoSeekFlag(), options), _starts (data, length) {}
|
|
||||||
|
|
||||||
bool checkValidity() const {
|
|
||||||
IteratorEntry* entry = _entry.get();
|
|
||||||
return entry->_lit->Valid() && entry->_lit->key().starts_with (leveldb::Slice (_starts.data(), _starts.length()));
|
|
||||||
}
|
|
||||||
virtual void increment() override {
|
|
||||||
IteratorEntry* entry = _entry.get();
|
|
||||||
if (entry->_valid) entry->_lit->Next();
|
|
||||||
else entry->_lit->Seek (leveldb::Slice (_starts.data(), _starts.length()));
|
|
||||||
entry->_valid = checkValidity();
|
|
||||||
}
|
|
||||||
virtual void decrement() override {
|
|
||||||
IteratorEntry* entry = _entry.get();
|
|
||||||
if (entry->_valid) entry->_lit->Prev(); else seekLast();
|
|
||||||
entry->_valid = checkValidity();
|
|
||||||
}
|
|
||||||
void seekLast() {
|
|
||||||
leveldb::Iterator* lit = _entry->_lit;
|
|
||||||
// Go somewhere *below* the `_starts` prefix.
|
|
||||||
char after[_starts.length()]; if (sizeof (after)) {
|
|
||||||
memcpy (after, _starts.data(), sizeof (after));
|
|
||||||
uint32_t pos = sizeof (after); while (--pos >= 0) if (after[pos] < CHAR_MAX) {++after[pos]; break;}
|
|
||||||
if (pos >= 0) {lit->Seek (leveldb::Slice (after, sizeof (after))); if (!lit->Valid()) lit->SeekToLast();} else lit->SeekToLast();
|
|
||||||
} else lit->SeekToLast();
|
|
||||||
// Seek back until we are in the `_starts` prefix.
|
|
||||||
for (leveldb::Slice prefix (_starts.data(), _starts.length()); lit->Valid(); lit->Prev()) {
|
|
||||||
leveldb::Slice key (lit->key());
|
|
||||||
if (key.starts_with (prefix)) break; // We're "back" in the `_starts` prefix.
|
|
||||||
if (key.compare (prefix) < 0) break; // Gone too far (no prefix entries in the db).
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Range over entries starting with `key`. */
|
|
||||||
template <typename K>
|
|
||||||
boost::iterator_range<StartsWithIterator> startsWith (const K& key) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
ldbSerialize (kbytes, key);
|
|
||||||
return boost::iterator_range<StartsWithIterator> (
|
|
||||||
StartsWithIterator (this, kbytes.data(), kbytes.length()),
|
|
||||||
StartsWithIterator (this, kbytes.data(), kbytes.length(), NoSeekFlag()));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Trigger {
|
|
||||||
virtual gstring triggerName() const {return C2GSTRING ("defaultTriggerName");};
|
|
||||||
virtual void put (Ldb& ldb, void* key, gstring& kbytes, void* value, gstring& vbytes, leveldb::WriteBatch& batch) = 0;
|
|
||||||
virtual void del (Ldb& ldb, void* key, gstring& kbytes, leveldb::WriteBatch& batch) = 0;
|
|
||||||
};
|
|
||||||
std::map<gstring, std::shared_ptr<Trigger>> _triggers;
|
|
||||||
|
|
||||||
/** Register the trigger (by its `triggerName`). */
|
|
||||||
void putTrigger (std::shared_ptr<Trigger> trigger) {
|
|
||||||
_triggers[trigger->triggerName()] = trigger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
Ldb() {}
|
|
||||||
|
|
||||||
/** Opens Leveldb database. */
|
|
||||||
Ldb (const char* path, leveldb::Options* options = nullptr, mode_t mode = 0770) {
|
|
||||||
int rc = ::mkdir (path, mode);
|
|
||||||
if (rc && errno != EEXIST) GNTHROW (LdbEx, std::string ("Can't create ") + path + ": " + ::strerror (errno));
|
|
||||||
leveldb::DB* db;
|
|
||||||
leveldb::Status status;
|
|
||||||
if (options) {
|
|
||||||
status = leveldb::DB::Open (*options, path, &db);
|
|
||||||
} else {
|
|
||||||
leveldb::Options localOptions;
|
|
||||||
localOptions.create_if_missing = true;
|
|
||||||
_filter.reset (leveldb::NewBloomFilterPolicy (8));
|
|
||||||
localOptions.filter_policy = _filter.get();
|
|
||||||
status = leveldb::DB::Open (localOptions, path, &db);
|
|
||||||
}
|
|
||||||
if (!status.ok()) GNTHROW (LdbEx, std::string ("Ldb: Can't open ") + path + ": " + status.ToString());
|
|
||||||
_db.reset (db);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Wraps an existing Leveldb handler. */
|
|
||||||
Ldb (std::shared_ptr<leveldb::DB> db): _db (db) {}
|
|
||||||
|
|
||||||
template <typename K, typename V> void put (const K& key, const V& value, leveldb::WriteBatch& batch) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
ldbSerialize (kbytes, key);
|
|
||||||
|
|
||||||
char vbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring vbytes (sizeof (vbuf), vbuf, false, 0);
|
|
||||||
ldbSerialize (vbytes, value);
|
|
||||||
|
|
||||||
for (auto& trigger: _triggers) trigger.second->put (*this, (void*) &key, kbytes, (void*) &value, vbytes, batch);
|
|
||||||
|
|
||||||
batch.Put (leveldb::Slice (kbytes.data(), kbytes.size()), leveldb::Slice (vbytes.data(), vbytes.size()));
|
|
||||||
}
|
|
||||||
template <typename K, typename V> void put (const K& key, const V& value) {
|
|
||||||
leveldb::WriteBatch batch;
|
|
||||||
put (key, value, batch);
|
|
||||||
leveldb::Status status (_db->Write (leveldb::WriteOptions(), &batch));
|
|
||||||
if (!status.ok()) GNTHROW (LdbEx, "Ldb: add: " + status.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns `true` if the key exists. Throws on error. */
|
|
||||||
template <typename K> bool have (const K& key, leveldb::ReadOptions options = leveldb::ReadOptions()) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
ldbSerialize (kbytes, key);
|
|
||||||
leveldb::Slice keySlice (kbytes.data(), kbytes.size());
|
|
||||||
|
|
||||||
// NB: "BloomFilter only helps for Get() calls" - https://groups.google.com/d/msg/leveldb/oEiDztqHiHc/LMY3tHxzRGAJ
|
|
||||||
// "Apart from the lack of Bloom filter functionality, creating an iterator is really quite slow" - qpu2jSA8mCEJ
|
|
||||||
std::string str;
|
|
||||||
leveldb::Status status (_db->Get (options, keySlice, &str));
|
|
||||||
if (status.ok()) return true;
|
|
||||||
else if (status.IsNotFound()) return false;
|
|
||||||
else GTHROW ("Ldb.have: " + status.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns `true` and modifies `value` if `key` is found. */
|
|
||||||
template <typename K, typename V> bool get (const K& key, V& value, leveldb::ReadOptions options = leveldb::ReadOptions()) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
ldbSerialize (kbytes, key);
|
|
||||||
leveldb::Slice keySlice (kbytes.data(), kbytes.size());
|
|
||||||
|
|
||||||
// NB: "BloomFilter only helps for Get() calls" - https://groups.google.com/d/msg/leveldb/oEiDztqHiHc/LMY3tHxzRGAJ
|
|
||||||
// "Apart from the lack of Bloom filter functionality, creating an iterator is really quite slow" - qpu2jSA8mCEJ
|
|
||||||
std::string str;
|
|
||||||
leveldb::Status status (_db->Get (options, keySlice, &str));
|
|
||||||
if (status.ok()) {
|
|
||||||
ldbDeserialize (gstring (0, (void*) str.data(), false, str.size()), value);
|
|
||||||
return true;
|
|
||||||
} else if (status.IsNotFound()) return false;
|
|
||||||
else GTHROW ("Ldb.get: " + status.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename K> void del (const K& key, leveldb::WriteBatch& batch) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
ldbSerialize (kbytes, key);
|
|
||||||
if (kbytes.empty()) GNTHROW (LdbEx, "del: key is empty");
|
|
||||||
|
|
||||||
for (auto& trigger: _triggers) trigger.second->del (*this, (void*) &key, kbytes, batch);
|
|
||||||
|
|
||||||
batch.Delete (leveldb::Slice (kbytes.data(), kbytes.size()));
|
|
||||||
}
|
|
||||||
template <typename K> void del (const K& key) {
|
|
||||||
leveldb::WriteBatch batch;
|
|
||||||
del (key, batch);
|
|
||||||
leveldb::Status status (_db->Write (leveldb::WriteOptions(), &batch));
|
|
||||||
if (!status.ok()) GNTHROW (LdbEx, "Ldb: del: " + status.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Writes the batch. Throws LdbEx if not successfull. */
|
|
||||||
void write (leveldb::WriteBatch& batch, leveldb::WriteOptions options = leveldb::WriteOptions()) {
|
|
||||||
leveldb::Status status (_db->Write (options, &batch));
|
|
||||||
if (!status.ok()) GNTHROW (LdbEx, status.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~Ldb() {
|
|
||||||
_triggers.clear(); // Destroy triggers before closing the database.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace glim
|
|
||||||
|
|
||||||
#endif // _GLIM_LDB_HPP_INCLUDED
|
|
93
external/glim/makefile
vendored
93
external/glim/makefile
vendored
|
@ -1,93 +0,0 @@
|
||||||
|
|
||||||
PREFIX = /usr/local
|
|
||||||
INSTALL2 = ${PREFIX}/include/glim
|
|
||||||
CXXFLAGS = -std=c++1y -Wall -O2 -ggdb -DBOOST_ALL_DYN_LINK
|
|
||||||
|
|
||||||
all: test
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "make test\nmake install\nmake uninstall\nmake clean"
|
|
||||||
|
|
||||||
doc: doxyconf *.hpp
|
|
||||||
mkdir -p doc
|
|
||||||
doxygen doxyconf
|
|
||||||
|
|
||||||
test: test_sqlite test_gstring test_runner test_exception test_ldb
|
|
||||||
|
|
||||||
test_sqlite: bin/test_sqlite
|
|
||||||
cp bin/test_sqlite /tmp/libglim_test_sqlite && chmod +x /tmp/libglim_test_sqlite && /tmp/libglim_test_sqlite && rm -f /tmp/libglim_test_sqlite
|
|
||||||
|
|
||||||
bin/test_sqlite: test_sqlite.cc
|
|
||||||
mkdir -p bin
|
|
||||||
g++ $(CXXFLAGS) test_sqlite.cc -o bin/test_sqlite -lsqlite3
|
|
||||||
|
|
||||||
test_memcache: bin/test_memcache
|
|
||||||
cp bin/test_memcache /tmp/libglim_test_memcache && chmod +x /tmp/libglim_test_memcache && /tmp/libglim_test_memcache && rm -f /tmp/libglim_test_memcache
|
|
||||||
|
|
||||||
bin/test_memcache: test_memcache.cc memcache.hpp
|
|
||||||
mkdir -p bin
|
|
||||||
g++ $(CXXFLAGS) test_memcache.cc -o bin/test_memcache -lmemcache
|
|
||||||
|
|
||||||
bin/test_gstring: test_gstring.cc gstring.hpp
|
|
||||||
mkdir -p bin
|
|
||||||
g++ $(CXXFLAGS) test_gstring.cc -o bin/test_gstring
|
|
||||||
|
|
||||||
test_gstring: bin/test_gstring
|
|
||||||
cp bin/test_gstring /tmp/libglim_test_gstring
|
|
||||||
chmod +x /tmp/libglim_test_gstring
|
|
||||||
/tmp/libglim_test_gstring
|
|
||||||
rm -f /tmp/libglim_test_gstring
|
|
||||||
|
|
||||||
bin/test_runner: test_runner.cc runner.hpp curl.hpp
|
|
||||||
mkdir -p bin
|
|
||||||
g++ $(CXXFLAGS) test_runner.cc -o bin/test_runner -pthread -lboost_log -levent -levent_pthreads -lcurl
|
|
||||||
|
|
||||||
test_runner: bin/test_runner
|
|
||||||
valgrind -q bin/test_runner
|
|
||||||
|
|
||||||
bin/test_exception: test_exception.cc exception.hpp
|
|
||||||
mkdir -p bin
|
|
||||||
g++ $(CXXFLAGS) test_exception.cc -o bin/test_exception -ldl -rdynamic
|
|
||||||
|
|
||||||
test_exception: bin/test_exception
|
|
||||||
valgrind -q bin/test_exception
|
|
||||||
|
|
||||||
test_ldb: test_ldb.cc ldb.hpp
|
|
||||||
mkdir -p bin
|
|
||||||
g++ $(CXXFLAGS) test_ldb.cc -o bin/test_ldb \
|
|
||||||
-lleveldb -lboost_serialization -lboost_filesystem -lboost_system
|
|
||||||
valgrind -q bin/test_ldb
|
|
||||||
|
|
||||||
bin/test_cbcoro: test_cbcoro.cc
|
|
||||||
mkdir -p bin
|
|
||||||
g++ $(CXXFLAGS) test_cbcoro.cc -o bin/test_cbcoro -pthread
|
|
||||||
|
|
||||||
test_cbcoro: bin/test_cbcoro
|
|
||||||
bin/test_cbcoro
|
|
||||||
|
|
||||||
install:
|
|
||||||
mkdir -p ${INSTALL2}/
|
|
||||||
cp sqlite.hpp ${INSTALL2}/
|
|
||||||
cp NsecTimer.hpp ${INSTALL2}/
|
|
||||||
cp TscTimer.hpp ${INSTALL2}/
|
|
||||||
cp memcache.hpp ${INSTALL2}/
|
|
||||||
cp gstring.hpp ${INSTALL2}/
|
|
||||||
cp runner.hpp ${INSTALL2}/
|
|
||||||
cp hget.hpp ${INSTALL2}/
|
|
||||||
cp curl.hpp ${INSTALL2}/
|
|
||||||
cp mdb.hpp ${INSTALL2}/
|
|
||||||
cp ldb.hpp ${INSTALL2}/
|
|
||||||
cp exception.hpp ${INSTALL2}/
|
|
||||||
cp SerializablePool.hpp ${INSTALL2}/
|
|
||||||
cp cbcoro.hpp ${INSTALL2}/
|
|
||||||
cp raii.hpp ${INSTALL2}/
|
|
||||||
cp channel.hpp ${INSTALL2}/
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
rm -rf ${INSTALL2}
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf bin/*
|
|
||||||
rm -rf doc
|
|
||||||
rm -f /tmp/libglim_test_*
|
|
||||||
rm -f *.exe.stackdump
|
|
499
external/glim/mdb.hpp
vendored
499
external/glim/mdb.hpp
vendored
|
@ -1,499 +0,0 @@
|
||||||
#ifndef _GLIM_MDB_HPP_INCLUDED
|
|
||||||
#define _GLIM_MDB_HPP_INCLUDED
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A C++ wrapper around MDB (http://www.symas.com/mdb/).
|
|
||||||
* @code
|
|
||||||
Copyright 2012 Kozarezov Artem Aleksandrovich
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
* @endcode
|
|
||||||
* @file
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mdb.h>
|
|
||||||
#include <boost/archive/binary_oarchive.hpp>
|
|
||||||
#include <boost/archive/binary_iarchive.hpp>
|
|
||||||
#include <boost/serialization/serialization.hpp>
|
|
||||||
#include <boost/noncopyable.hpp>
|
|
||||||
#include <boost/iterator/iterator_facade.hpp>
|
|
||||||
#include <boost/range/iterator_range.hpp>
|
|
||||||
|
|
||||||
#include <arpa/inet.h> // htonl, ntohl
|
|
||||||
|
|
||||||
#include "gstring.hpp"
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
struct MdbEx: public std::runtime_error {MdbEx (std::string message): std::runtime_error (message) {}};
|
|
||||||
|
|
||||||
template <typename T> inline void mdbSerialize (gstring& bytes, const T& data) {
|
|
||||||
gstring_stream stream (bytes);
|
|
||||||
boost::archive::binary_oarchive oa (stream, boost::archive::no_header);
|
|
||||||
oa << data;
|
|
||||||
}
|
|
||||||
template <typename V> inline void mdbDeserialize (const gstring& bytes, V& data) {
|
|
||||||
gstring_stream stream (const_cast<gstring&> (bytes));
|
|
||||||
boost::archive::binary_iarchive ia (stream, boost::archive::no_header);
|
|
||||||
ia >> data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** uint32_t keys are stored big-endian (network byte order) in order to be compatible with lexicographic ordering. */
|
|
||||||
template <> inline void mdbSerialize<uint32_t> (gstring& bytes, const uint32_t& ui) {
|
|
||||||
uint32_t nui = htonl (ui); bytes.append ((const char*) &nui, sizeof (uint32_t));}
|
|
||||||
/** Deserialize uint32_t from big-endian (network byte order). */
|
|
||||||
template <> inline void mdbDeserialize<uint32_t> (const gstring& bytes, uint32_t& ui) {
|
|
||||||
if (bytes.size() != sizeof (uint32_t)) throw MdbEx ("Not uint32_t, wrong number of bytes");
|
|
||||||
uint32_t nui = * (uint32_t*) bytes.data(); ui = ntohl (nui);}
|
|
||||||
|
|
||||||
/** If the data is `gstring` then use the data's buffer directly, no copy. */
|
|
||||||
template <> inline void mdbSerialize<gstring> (gstring& bytes, const gstring& data) {
|
|
||||||
bytes = gstring (0, (void*) data.data(), false, data.length());}
|
|
||||||
/** Deserializing into `gstring` copies the bytes into it, reusing its buffer. */
|
|
||||||
template <> inline void mdbDeserialize<gstring> (const gstring& bytes, gstring& data) {
|
|
||||||
data.clear() << bytes;}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Header-only C++ wrapper around OpenLDAP-MDB.\n
|
|
||||||
* Uses Boost Serialization to pack keys and values (glim::gstring can be used for raw bytes).\n
|
|
||||||
* Allows semi-automatic indexing with triggers.\n
|
|
||||||
* Known issues: http://www.openldap.org/its/index.cgi?findid=7448
|
|
||||||
*/
|
|
||||||
struct Mdb {
|
|
||||||
std::shared_ptr<MDB_env> _env;
|
|
||||||
MDB_dbi _dbi = 0;
|
|
||||||
|
|
||||||
typedef std::unique_ptr<MDB_txn, void(*)(MDB_txn*)> Transaction;
|
|
||||||
|
|
||||||
/** Holds the current key and value of the Iterator. */
|
|
||||||
struct IteratorEntry {
|
|
||||||
MDB_val _key = {0, 0}, _val = {0, 0};
|
|
||||||
/** Zero-copy view of the current key bytes. Should *not* be used after the Iterator is changed or destroyed. */
|
|
||||||
const gstring keyView() const {return gstring (0, _key.mv_data, false, _key.mv_size, true);} // Zero copy.
|
|
||||||
/** Zero-copy view of the current value bytes. Should *not* be used after the Iterator is changed or destroyed. */
|
|
||||||
const gstring valueView() const {return gstring (0, _val.mv_data, false, _val.mv_size, true);} // Zero copy.
|
|
||||||
/** Deserialize into `key`. */
|
|
||||||
template <typename T> void getKey (T& key) const {mdbDeserialize (keyView(), key);}
|
|
||||||
/** Deserialize the key into a temporary and return it. */
|
|
||||||
template <typename T> T getKey() const {T key; getKey (key); return key;}
|
|
||||||
/** Deserialize into `value`. */
|
|
||||||
template <typename T> void getValue (T& value) const {mdbDeserialize (valueView(), value);}
|
|
||||||
/** Deserialize the value into a temporary and return it. */
|
|
||||||
template <typename T> T getValue() const {T value; getValue (value); return value;}
|
|
||||||
};
|
|
||||||
/** Holds the Iterator's unique transaction and cursor, allowing the Iterator to be copied. */
|
|
||||||
struct IteratorImpl: boost::noncopyable {
|
|
||||||
Mdb* _mdb;
|
|
||||||
Transaction _txn;
|
|
||||||
MDB_cursor* _cur;
|
|
||||||
IteratorImpl (Mdb* mdb, Transaction&& txn, MDB_cursor* cur): _mdb (mdb), _txn (std::move (txn)), _cur (cur) {}
|
|
||||||
~IteratorImpl() {
|
|
||||||
if (_cur) {::mdb_cursor_close (_cur); _cur = nullptr;}
|
|
||||||
if (_mdb && _txn) {_mdb->commitTransaction (_txn); _mdb = nullptr;}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/** Wraps MDB cursor and cursor's transaction. */
|
|
||||||
struct Iterator: public boost::iterator_facade<Iterator, IteratorEntry, boost::bidirectional_traversal_tag> {
|
|
||||||
std::shared_ptr<IteratorImpl> _impl; // Iterator might be copied around, thus we keep the unique things in IteratorImpl.
|
|
||||||
IteratorEntry _entry;
|
|
||||||
bool _stayInKey = false;
|
|
||||||
|
|
||||||
Iterator (const Iterator&) = default;
|
|
||||||
Iterator (Iterator&&) = default;
|
|
||||||
/** Iterate from the beginning or the end of the database.
|
|
||||||
* @param position can be MDB_FIRST or MDB_LAST */
|
|
||||||
Iterator (Mdb* mdb, int position = 0): _stayInKey (false) {
|
|
||||||
Transaction txn (mdb->beginTransaction());
|
|
||||||
MDB_cursor* cur = nullptr; int rc = ::mdb_cursor_open (txn.get(), mdb->_dbi, &cur);
|
|
||||||
if (rc) throw MdbEx ("mdb_cursor_open");
|
|
||||||
_impl = std::make_shared<IteratorImpl> (mdb, std::move (txn), cur);
|
|
||||||
if (position == ::MDB_FIRST || position == ::MDB_LAST) {
|
|
||||||
rc = ::mdb_cursor_get (cur, &_entry._key, &_entry._val, (MDB_cursor_op) position);
|
|
||||||
if (rc) throw MdbEx ("mdb_cursor_get");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Iterate over `key` values.
|
|
||||||
* @param stayInKey if `false` then iterator can go farther than the `key`. */
|
|
||||||
Iterator (Mdb* mdb, const gstring& key, bool stayInKey = true): _stayInKey (stayInKey) {
|
|
||||||
Transaction txn (mdb->beginTransaction());
|
|
||||||
MDB_cursor* cur = nullptr; int rc = ::mdb_cursor_open (txn.get(), mdb->_dbi, &cur);
|
|
||||||
if (rc) throw MdbEx ("mdb_cursor_open");
|
|
||||||
_impl = std::make_shared<IteratorImpl> (mdb, std::move (txn), cur);
|
|
||||||
_entry._key = {key.size(), (void*) key.data()};
|
|
||||||
rc = ::mdb_cursor_get (cur, &_entry._key, &_entry._val, ::MDB_SET_KEY);
|
|
||||||
if (rc == MDB_NOTFOUND) {_entry._key = {0, 0}; _entry._val = {0, 0};}
|
|
||||||
else if (rc) throw MdbEx ("mdb_cursor_get");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EndIteratorFlag {};
|
|
||||||
/** The "end" iterator does not open an MDB transaction (this is essential for having a pair of iterators without a deadlock). */
|
|
||||||
Iterator (EndIteratorFlag): _stayInKey (false) {}
|
|
||||||
/** True if the iterator isn't pointing anywhere. */
|
|
||||||
bool end() const {return _entry._key.mv_size == 0;}
|
|
||||||
|
|
||||||
bool equal (const Iterator& other) const {
|
|
||||||
IteratorImpl* impl = _impl.get();
|
|
||||||
if (mdb_cmp (impl->_txn.get(), impl->_mdb->_dbi, &_entry._key, &other._entry._key)) return false;
|
|
||||||
if (mdb_dcmp (impl->_txn.get(), impl->_mdb->_dbi, &_entry._val, &other._entry._val)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
IteratorEntry& dereference() const {
|
|
||||||
// NB: Boost iterator_facade expects the `dereference` to be a `const` method.
|
|
||||||
// I guess Iterator is not modified, so the `dereference` is `const`, even though the Entry can be modified.
|
|
||||||
return const_cast<IteratorEntry&> (_entry);}
|
|
||||||
void increment() {
|
|
||||||
int rc = ::mdb_cursor_get (_impl->_cur, &_entry._key, &_entry._val, _stayInKey ? ::MDB_NEXT_DUP : ::MDB_NEXT);
|
|
||||||
if (rc) {_entry._key = {0,0}; _entry._val = {0,0};}
|
|
||||||
}
|
|
||||||
void decrement() {
|
|
||||||
int rc = ::mdb_cursor_get (_impl->_cur, &_entry._key, &_entry._val, _stayInKey ? ::MDB_PREV_DUP : ::MDB_PREV);
|
|
||||||
if (rc) {_entry._key = {0,0}; _entry._val = {0,0};}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Iterator begin() {return Iterator (this, ::MDB_FIRST);}
|
|
||||||
const Iterator end() {return Iterator (Iterator::EndIteratorFlag());}
|
|
||||||
/** Position the cursor at the first `key` record.\n
|
|
||||||
* The iterator increment will use `MDB_NEXT_DUP`, staying withing the `key`.\n
|
|
||||||
* See also the `all` method. */
|
|
||||||
template <typename K> Iterator values (const K& key) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
mdbSerialize (kbytes, key);
|
|
||||||
return Iterator (this, kbytes);
|
|
||||||
}
|
|
||||||
/** Range over the `key` values.\n
|
|
||||||
* See also the `all` method. */
|
|
||||||
template <typename K> boost::iterator_range<Iterator> valuesRange (const K& key) {return boost::iterator_range<Iterator> (values (key), end());}
|
|
||||||
|
|
||||||
struct Trigger {
|
|
||||||
virtual gstring getTriggerName() const {return C2GSTRING ("defaultTriggerName");};
|
|
||||||
virtual void add (Mdb& mdb, void* key, gstring& kbytes, void* value, gstring& vbytes, Transaction& txn) = 0;
|
|
||||||
virtual void erase (Mdb& mdb, void* key, gstring& kbytes, Transaction& txn) = 0;
|
|
||||||
virtual void eraseKV (Mdb& mdb, void* key, gstring& kbytes, void* value, gstring& vbytes, Transaction& txn) = 0;
|
|
||||||
};
|
|
||||||
std::map<gstring, std::shared_ptr<Trigger>> _triggers;
|
|
||||||
|
|
||||||
void setTrigger (std::shared_ptr<Trigger> trigger) {
|
|
||||||
_triggers[trigger->getTriggerName()] = trigger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** `flags` can be `MDB_RDONLY` */
|
|
||||||
Transaction beginTransaction (unsigned flags = 0) {
|
|
||||||
MDB_txn* txn = 0; int rc = ::mdb_txn_begin (_env.get(), nullptr, flags, &txn);
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_txn_begin: ") + ::strerror (rc));
|
|
||||||
return Transaction (txn, ::mdb_txn_abort);
|
|
||||||
}
|
|
||||||
void commitTransaction (Transaction& txn) {
|
|
||||||
int rc = ::mdb_txn_commit (txn.get());
|
|
||||||
txn.release(); // Must prevent `mdb_txn_abort` from happening (even if rc != 0).
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_txn_commit: ") + ::strerror (rc));
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual unsigned envFlags (uint8_t sync) {
|
|
||||||
unsigned flags = MDB_NOSUBDIR;
|
|
||||||
if (sync < 1) flags |= MDB_NOSYNC; else if (sync < 2) flags |= MDB_NOMETASYNC;
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
/** Used before `mdb_env_open`. By default sets the number of database to 32. */
|
|
||||||
virtual void envConf (MDB_env* env) {
|
|
||||||
int rc = ::mdb_env_set_maxdbs (env, 32);
|
|
||||||
if (rc) throw MdbEx (std::string ("envConf: ") + ::strerror (rc));
|
|
||||||
}
|
|
||||||
virtual void dbFlags (unsigned& flags) {}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void open (const char* dbName, bool dup) {
|
|
||||||
auto txn = beginTransaction();
|
|
||||||
unsigned flags = MDB_CREATE;
|
|
||||||
if (dup) flags |= MDB_DUPSORT;
|
|
||||||
dbFlags (flags);
|
|
||||||
int rc = ::mdb_open (txn.get(), dbName, flags, &_dbi);
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_open (") + dbName + "): " + ::strerror (rc));
|
|
||||||
commitTransaction (txn);
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
|
|
||||||
/** Opens MDB environment and MDB database. */
|
|
||||||
Mdb (const char* path, size_t maxSizeMb = 1024, const char* dbName = "main", uint8_t sync = 0, bool dup = true, mode_t mode = 0660) {
|
|
||||||
MDB_env* env = 0; int rc = ::mdb_env_create (&env);
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_env_create: ") + ::strerror (rc));
|
|
||||||
_env.reset (env, ::mdb_env_close);
|
|
||||||
rc = ::mdb_env_set_mapsize (env, maxSizeMb * 1024 * 1024);
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_env_set_mapsize: ") + ::strerror (rc));
|
|
||||||
envConf (env);
|
|
||||||
rc = ::mdb_env_open (env, path, envFlags (sync), mode);
|
|
||||||
_dbi = 0; open (dbName, dup);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Opens MDB database in the provided environment. */
|
|
||||||
Mdb (std::shared_ptr<MDB_env> env, const char* dbName, bool dup = true): _env (env), _dbi (0) {
|
|
||||||
open (dbName, dup);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename K, typename V> void add (const K& key, const V& value, Transaction& txn) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
mdbSerialize (kbytes, key);
|
|
||||||
MDB_val mkey = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
|
|
||||||
char vbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring vbytes (sizeof (vbuf), vbuf, false, 0);
|
|
||||||
mdbSerialize (vbytes, value);
|
|
||||||
MDB_val mvalue = {vbytes.size(), (void*) vbytes.data()};
|
|
||||||
|
|
||||||
for (auto& trigger: _triggers) trigger.second->add (*this, (void*) &key, kbytes, (void*) &value, vbytes, txn);
|
|
||||||
|
|
||||||
int rc = ::mdb_put (txn.get(), _dbi, &mkey, &mvalue, 0);
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_put: ") + ::strerror (rc));
|
|
||||||
}
|
|
||||||
template <typename K, typename V> void add (const K& key, const V& value) {
|
|
||||||
Transaction txn (beginTransaction());
|
|
||||||
add (key, value, txn);
|
|
||||||
commitTransaction (txn);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename K, typename V> bool first (const K& key, V& value, Transaction& txn) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
mdbSerialize (kbytes, key);
|
|
||||||
MDB_val mkey = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
MDB_val mvalue;
|
|
||||||
int rc = ::mdb_get (txn.get(), _dbi, &mkey, &mvalue);
|
|
||||||
if (rc == MDB_NOTFOUND) return false;
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_get: ") + ::strerror (rc));
|
|
||||||
gstring vstr (0, mvalue.mv_data, false, mvalue.mv_size);
|
|
||||||
mdbDeserialize (vstr, value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
template <typename K, typename V> bool first (const K& key, V& value) {
|
|
||||||
Transaction txn (beginTransaction (MDB_RDONLY));
|
|
||||||
bool rb = first (key, value, txn);
|
|
||||||
commitTransaction (txn);
|
|
||||||
return rb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Iterate over `key` values until `visitor` returns `false`. Return the number of values visited. */
|
|
||||||
template <typename K, typename V> int32_t all (const K& key, std::function<bool(const V&)> visitor, Transaction& txn) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
mdbSerialize (kbytes, key);
|
|
||||||
MDB_val mkey = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
|
|
||||||
MDB_cursor* cur = 0; int rc = ::mdb_cursor_open (txn.get(), _dbi, &cur);
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_cursor_open: ") + ::strerror (rc));
|
|
||||||
std::unique_ptr<MDB_cursor, void(*)(MDB_cursor*)> curHolder (cur, ::mdb_cursor_close);
|
|
||||||
MDB_val mval = {0, 0};
|
|
||||||
rc = ::mdb_cursor_get (cur, &mkey, &mval, ::MDB_SET_KEY); if (rc == MDB_NOTFOUND) return 0;
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_cursor_get: ") + ::strerror (rc));
|
|
||||||
|
|
||||||
V value;
|
|
||||||
gstring vstr (0, mval.mv_data, false, mval.mv_size);
|
|
||||||
mdbDeserialize (vstr, value);
|
|
||||||
bool goOn = visitor (value);
|
|
||||||
int32_t count = 1;
|
|
||||||
|
|
||||||
while (goOn) {
|
|
||||||
rc = ::mdb_cursor_get (cur, &mkey, &mval, ::MDB_NEXT_DUP); if (rc == MDB_NOTFOUND) return count;
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_cursor_get: ") + ::strerror (rc));
|
|
||||||
gstring vstr (0, mval.mv_data, false, mval.mv_size);
|
|
||||||
mdbDeserialize (vstr, value);
|
|
||||||
goOn = visitor (value);
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
/** Iterate over `key` values until `visitor` returns `false`. Return the number of values visited. */
|
|
||||||
template <typename K, typename V> int32_t all (const K& key, std::function<bool(const V&)> visitor) {
|
|
||||||
Transaction txn (beginTransaction (MDB_RDONLY));
|
|
||||||
int32_t count = all (key, visitor, txn);
|
|
||||||
commitTransaction (txn);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename K, typename V> bool eraseKV (const K& key, const V& value, Transaction& txn) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
mdbSerialize (kbytes, key);
|
|
||||||
if (kbytes.empty()) throw MdbEx ("eraseKV: key is empty");
|
|
||||||
MDB_val mkey = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
|
|
||||||
char vbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring vbytes (sizeof (vbuf), vbuf, false, 0);
|
|
||||||
mdbSerialize (vbytes, value);
|
|
||||||
MDB_val mvalue = {vbytes.size(), (void*) vbytes.data()};
|
|
||||||
|
|
||||||
for (auto& trigger: _triggers) trigger.second->eraseKV (*this, (void*) &key, kbytes, (void*) &value, vbytes, txn);
|
|
||||||
|
|
||||||
int rc = ::mdb_del (txn.get(), _dbi, &mkey, &mvalue);
|
|
||||||
if (rc == MDB_NOTFOUND) return false;
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_del: ") + ::strerror (rc));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
template <typename K, typename V> bool eraseKV (const K& key, const V& value) {
|
|
||||||
Transaction txn (beginTransaction());
|
|
||||||
bool rb = eraseKV (key, value, txn);
|
|
||||||
commitTransaction (txn);
|
|
||||||
return rb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Erase all values of the `key`. */
|
|
||||||
template <typename K> bool erase (const K& key, Transaction& txn) {
|
|
||||||
char kbuf[64]; // Allow up to 64 bytes to be serialized without heap allocations.
|
|
||||||
gstring kbytes (sizeof (kbuf), kbuf, false, 0);
|
|
||||||
mdbSerialize (kbytes, key);
|
|
||||||
if (kbytes.empty()) throw MdbEx ("erase: key is empty");
|
|
||||||
MDB_val mkey = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
|
|
||||||
for (auto& trigger: _triggers) trigger.second->erase (*this, (void*) &key, kbytes, txn);
|
|
||||||
|
|
||||||
int rc = ::mdb_del (txn.get(), _dbi, &mkey, nullptr);
|
|
||||||
if (rc == MDB_NOTFOUND) return false;
|
|
||||||
if (rc) throw MdbEx (std::string ("mdb_del: ") + ::strerror (rc));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/** Erase all values of the `key`. */
|
|
||||||
template <typename K> bool erase (const K& key) {
|
|
||||||
Transaction txn (beginTransaction());
|
|
||||||
bool rb = erase (key, txn);
|
|
||||||
commitTransaction (txn);
|
|
||||||
return rb;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test (Mdb& mdb) {
|
|
||||||
mdb.add (std::string ("foo"), std::string ("bar"));
|
|
||||||
// NB: "MDB_DUPSORT doesn't allow duplicate duplicates" (Howard Chu)
|
|
||||||
mdb.add (std::string ("foo"), std::string ("bar"));
|
|
||||||
mdb.add ((uint32_t) 123, 1);
|
|
||||||
mdb.add ((uint32_t) 123, 2);
|
|
||||||
mdb.add (C2GSTRING ("foo"), 3);
|
|
||||||
mdb.add (C2GSTRING ("foo"), 4);
|
|
||||||
mdb.add (C2GSTRING ("gsk"), C2GSTRING ("gsv"));
|
|
||||||
string ts; int ti; gstring tgs;
|
|
||||||
auto fail = [](string msg) {throw std::runtime_error ("assertion failed: " + msg);};
|
|
||||||
if (!mdb.first (std::string ("foo"), ts) || ts != "bar") fail ("!foo=bar");
|
|
||||||
if (!mdb.first ((uint32_t) 123, ti) || ti < 1 || ti > 2) fail ("!123");
|
|
||||||
if (!mdb.first (C2GSTRING ("foo"), ti) || ti < 3 || ti > 4) fail ("!foo=3,4");
|
|
||||||
if (!mdb.first (C2GSTRING ("gsk"), tgs) || tgs != "gsv") fail ("!gsk=gsv");
|
|
||||||
|
|
||||||
// Test range-based for.
|
|
||||||
int count = 0; bool haveGskGsv = false;
|
|
||||||
for (auto&& entry: mdb) {
|
|
||||||
if (!entry._key.mv_size || !entry._val.mv_size) fail ("!entry");
|
|
||||||
if (entry.keyView() == "gsk") {
|
|
||||||
if (entry.getKey<gstring>() != "gsk") fail ("getKey(gsk)!=gsk");
|
|
||||||
if (entry.getValue<gstring>() != "gsv") fail ("getValue(gsk)!=gsv");
|
|
||||||
haveGskGsv = true;
|
|
||||||
}
|
|
||||||
++count;}
|
|
||||||
if (count != 6) fail ("count!=6"); // foo=bar, 123=1, 123=2, foo=3, foo=4, gsk=gsv
|
|
||||||
if (!haveGskGsv) fail ("!haveGskGsv");
|
|
||||||
|
|
||||||
// Test `values`.
|
|
||||||
count = 0; int sum = 0;
|
|
||||||
for (auto&& entry: mdb.valuesRange ((uint32_t) 123)) {
|
|
||||||
if (entry.getKey<uint32_t>() != (uint32_t) 123) fail("values(123).key!=123");
|
|
||||||
++count; sum += entry.getValue<int>();
|
|
||||||
}
|
|
||||||
if (count != 2) fail ("count(123)!=2");
|
|
||||||
if (sum != 3) fail ("sum(123)!=3");
|
|
||||||
|
|
||||||
if (!mdb.eraseKV ((uint32_t) 123, 1)) fail ("!eraseKV(123,1)");
|
|
||||||
if (!mdb.first ((uint32_t) 123, ti) || ti != 2) fail ("!123=2");
|
|
||||||
if (!mdb.eraseKV ((uint32_t) 123, 2)) fail ("!eraseKV(123,2)");
|
|
||||||
if (mdb.first ((uint32_t) 123, ti)) fail ("123");
|
|
||||||
|
|
||||||
if (!mdb.erase (C2GSTRING ("foo"))) fail ("!erase(g(foo))");
|
|
||||||
if (mdb.first (C2GSTRING ("foo"), ti)) fail ("foo");
|
|
||||||
if (!mdb.erase (std::string ("foo"))) fail ("!erase(str(foo))");
|
|
||||||
|
|
||||||
{ // We've erased "123" and "foo", the only key left is "gsk" (gsk=gsv), let's test the iterator boundaries on this small dataset.
|
|
||||||
auto&& it = mdb.begin();
|
|
||||||
if (it->getKey<gstring>() != "gsk") fail ("first key !gsk " + it->keyView().str());
|
|
||||||
if (!(++it).end()) fail ("++it != end");
|
|
||||||
if ((--it)->getKey<gstring>() != "gsk") fail ("can't go back to gsk");
|
|
||||||
if (!(--it).end()) fail ("--it != end");
|
|
||||||
if ((++it)->getKey<gstring>() != "gsk") fail ("can't go forward to gsk");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SimpleIndexTrigger: public Trigger {
|
|
||||||
const char* _name; Mdb _indexDb;
|
|
||||||
SimpleIndexTrigger (Mdb& mdb, const char* name = "index"): _name (name), _indexDb (mdb._env, name) {}
|
|
||||||
gstring getTriggerName() {return gstring (0, (void*) _name, false, strlen (_name), true);}
|
|
||||||
void add (Mdb& mdb, void* key, gstring& kbytes, void* value, gstring& vbytes, Transaction& txn) {
|
|
||||||
MDB_val mkey = {vbytes.size(), (void*) vbytes.data()};
|
|
||||||
MDB_val mvalue = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
int rc = ::mdb_put (txn.get(), _indexDb._dbi, &mkey, &mvalue, 0);
|
|
||||||
if (rc) throw MdbEx (std::string ("index, mdb_put: ") + ::strerror (rc));
|
|
||||||
}
|
|
||||||
void erase (Mdb& mdb, void* ekey, gstring& kbytes, Transaction& txn) {
|
|
||||||
// Get all the values and remove them from the index.
|
|
||||||
MDB_cursor* cur = 0; int rc = ::mdb_cursor_open (txn.get(), mdb._dbi, &cur);
|
|
||||||
if (rc) throw MdbEx (std::string ("index, erase, mdb_cursor_open: ") + ::strerror (rc));
|
|
||||||
std::unique_ptr<MDB_cursor, void(*)(MDB_cursor*)> curHolder (cur, ::mdb_cursor_close);
|
|
||||||
MDB_val mkey = {kbytes.size(), (void*) kbytes.data()}, val = {0, 0};
|
|
||||||
rc = ::mdb_cursor_get (cur, &mkey, &val, ::MDB_SET_KEY); if (rc == MDB_NOTFOUND) return;
|
|
||||||
if (rc) throw MdbEx (std::string ("index, erase, mdb_cursor_get: ") + ::strerror (rc));
|
|
||||||
rc = ::mdb_del (txn.get(), _indexDb._dbi, &val, &mkey);
|
|
||||||
if (rc && rc != MDB_NOTFOUND) throw MdbEx (std::string ("index, erase, mdb_del: ") + ::strerror (rc));
|
|
||||||
for (;;) {
|
|
||||||
rc = ::mdb_cursor_get (cur, &mkey, &val, ::MDB_NEXT_DUP); if (rc == MDB_NOTFOUND) return;
|
|
||||||
if (rc) throw MdbEx (std::string ("index, erase, mdb_cursor_get: ") + ::strerror (rc));
|
|
||||||
rc = ::mdb_del (txn.get(), _indexDb._dbi, &val, &mkey);
|
|
||||||
if (rc && rc != MDB_NOTFOUND) throw MdbEx (std::string ("index, erase, mdb_del: ") + ::strerror (rc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void eraseKV (Mdb& mdb, void* key, gstring& kbytes, void* value, gstring& vbytes, Transaction& txn) {
|
|
||||||
MDB_val mkey = {vbytes.size(), (void*) vbytes.data()};
|
|
||||||
MDB_val mvalue = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
int rc = ::mdb_del (txn.get(), _indexDb._dbi, &mkey, &mvalue);
|
|
||||||
if (rc && rc != MDB_NOTFOUND) throw MdbEx (std::string ("index, mdb_del: ") + ::strerror (rc));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
auto indexTrigger = std::make_shared<SimpleIndexTrigger> (mdb); mdb.setTrigger (indexTrigger); auto& indexDb = indexTrigger->_indexDb;
|
|
||||||
mdb.erase (C2GSTRING ("gsk")); // NB: "gsk" wasn't indexed here. `IndexTrigger.erase` should handle this gracefully.
|
|
||||||
|
|
||||||
// Add indexed.
|
|
||||||
mdb.add (C2GSTRING ("ik"), C2GSTRING ("iv1"));
|
|
||||||
mdb.add (C2GSTRING ("ik"), string ("iv2"));
|
|
||||||
mdb.add (C2GSTRING ("ik"), 3);
|
|
||||||
// Check the index.
|
|
||||||
gstring ik;
|
|
||||||
if (!indexDb.first (C2GSTRING ("iv1"), ik) || ik != "ik") fail ("!iv1=ik");
|
|
||||||
if (!indexDb.first (string ("iv2"), ik) || ik != "ik") fail ("!iv2=ik");
|
|
||||||
if (!indexDb.first (3, ik) || ik != "ik") fail ("!iv3=ik");
|
|
||||||
|
|
||||||
// Remove indexed.
|
|
||||||
mdb.eraseKV (C2GSTRING ("ik"), string ("iv2"));
|
|
||||||
// Check the index.
|
|
||||||
if (!indexDb.first (C2GSTRING ("iv1"), ik) || ik != "ik") fail ("!iv1=ik");
|
|
||||||
if (indexDb.first (string ("iv2"), ik)) fail ("iv2=ik");
|
|
||||||
if (!indexDb.first (3, ik) || ik != "ik") fail ("!iv3=ik");
|
|
||||||
|
|
||||||
// Remove indexed.
|
|
||||||
mdb.erase (C2GSTRING ("ik"));
|
|
||||||
// Check the index.
|
|
||||||
if (indexDb.first (C2GSTRING ("iv1"), ik)) fail ("iv1");
|
|
||||||
if (indexDb.first (3, ik)) fail ("iv3");
|
|
||||||
// Check the data.
|
|
||||||
if (mdb.first (C2GSTRING ("ik"), ik)) fail ("ik");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~Mdb() {
|
|
||||||
_triggers.clear(); // Destroy triggers before closing the database.
|
|
||||||
if (_dbi) {::mdb_close (_env.get(), _dbi); _dbi = 0;}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace glim
|
|
||||||
|
|
||||||
#endif // _GLIM_MDB_HPP_INCLUDED
|
|
3741
external/glim/ql2.pb.cc
vendored
3741
external/glim/ql2.pb.cc
vendored
File diff suppressed because it is too large
Load diff
2532
external/glim/ql2.pb.h
vendored
2532
external/glim/ql2.pb.h
vendored
File diff suppressed because it is too large
Load diff
34
external/glim/raii.hpp
vendored
34
external/glim/raii.hpp
vendored
|
@ -1,34 +0,0 @@
|
||||||
#include <functional>
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
// http://stackoverflow.com/questions/2121607/any-raii-template-in-boost-or-c0x/
|
|
||||||
|
|
||||||
/// RAII helper. Keeps the functor and runs it in the destructor.
|
|
||||||
/// Example: \code auto unmap = raiiFun ([&]() {munmap (fd, size);}); \endcode
|
|
||||||
template<typename Fun> struct RAIIFun {
|
|
||||||
Fun _fun;
|
|
||||||
RAIIFun (RAIIFun&&) = default;
|
|
||||||
RAIIFun (const RAIIFun&) = default;
|
|
||||||
template<typename FunArg> RAIIFun (FunArg&& fun): _fun (std::forward<Fun> (fun)) {}
|
|
||||||
~RAIIFun() {_fun();}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The idea to name it `finally` comes from http://www.codeproject.com/Tips/476970/finally-clause-in-Cplusplus.
|
|
||||||
/// Example: \code finally unmap ([&]() {munmap (fd, size);}); \endcode
|
|
||||||
typedef RAIIFun<std::function<void(void)>> finally;
|
|
||||||
|
|
||||||
/// Runs the given functor when going out of scope.
|
|
||||||
/// Example: \code
|
|
||||||
/// auto closeFd = raiiFun ([&]() {close (fd);});
|
|
||||||
/// auto unmap = raiiFun ([&]() {munmap (fd, size);});
|
|
||||||
/// \endcode
|
|
||||||
template<typename Fun> RAIIFun<Fun> raiiFun (const Fun& fun) {return RAIIFun<Fun> (fun);}
|
|
||||||
|
|
||||||
/// Runs the given functor when going out of scope.
|
|
||||||
/// Example: \code
|
|
||||||
/// auto closeFd = raiiFun ([&]() {close (fd);});
|
|
||||||
/// auto unmap = raiiFun ([&]() {munmap (fd, size);});
|
|
||||||
/// \endcode
|
|
||||||
template<typename Fun> RAIIFun<Fun> raiiFun (Fun&& fun) {return RAIIFun<Fun> (std::move (fun));}
|
|
||||||
|
|
||||||
}
|
|
402
external/glim/runner.hpp
vendored
402
external/glim/runner.hpp
vendored
|
@ -1,402 +0,0 @@
|
||||||
#ifndef _GLIM_RUNNER_INCLUDED
|
|
||||||
#define _GLIM_RUNNER_INCLUDED
|
|
||||||
|
|
||||||
#include <algorithm> // min
|
|
||||||
#include <atomic>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <chrono>
|
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
|
||||||
#include <memory>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <thread>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include <curl/curl.h>
|
|
||||||
#include <event2/event.h> // cf. hiperfifo.cpp at http://article.gmane.org/gmane.comp.web.curl.library/37752
|
|
||||||
|
|
||||||
#include <boost/intrusive_ptr.hpp>
|
|
||||||
#include <boost/lockfree/queue.hpp> // http://www.boost.org/doc/libs/1_53_0/doc/html/boost/lockfree/queue.html
|
|
||||||
#include <boost/log/trivial.hpp>
|
|
||||||
|
|
||||||
#include <time.h>
|
|
||||||
#include <stdlib.h> // rand
|
|
||||||
#include <sys/eventfd.h>
|
|
||||||
|
|
||||||
#include "gstring.hpp"
|
|
||||||
#include "exception.hpp"
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
/// Listens to messages returned by `curl_multi_info_read`.
|
|
||||||
/// NB: When CURL is queued with `addToCURLM` the CURL's `CURLOPT_PRIVATE` must point to the instance of `CurlmInformationListener`.
|
|
||||||
struct CurlmInformationListener {
|
|
||||||
enum FreeOptions {REMOVE_CURL_FROM_CURLM = 1, CURL_CLEANUP = 2, DELETE_LISTENER = 4, REMOVE_CLEAN_DELETE = 1|2|4};
|
|
||||||
virtual FreeOptions information (CURLMsg*, CURLM*) = 0;
|
|
||||||
virtual ~CurlmInformationListener() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Listener deferring to a lambda.
|
|
||||||
struct FunCurlmLisneter: public glim::CurlmInformationListener {
|
|
||||||
std::function <void(CURLMsg*, CURLM*)> _fun;
|
|
||||||
FreeOptions _freeOptions;
|
|
||||||
FunCurlmLisneter (std::function <void(CURLMsg*, CURLM*)>&& fun, FreeOptions freeOptions): _fun (std::move (fun)), _freeOptions (freeOptions) {}
|
|
||||||
virtual FreeOptions information (CURLMsg* msg, CURLM* curlm) override {
|
|
||||||
if (__builtin_expect ((bool) _fun, 1))
|
|
||||||
try {_fun (msg, curlm);} catch (const std::exception& ex) {BOOST_LOG_TRIVIAL (error) << "FunCurlmLisneter] " << ex.what();}
|
|
||||||
return _freeOptions;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Running cURL jobs in a single thread.
|
|
||||||
/// NB: The RunnerV2 *must* be allocated with `boost::intrusive_ptr` (typically you'd use `RunnerV2::instance()`).
|
|
||||||
class RunnerV2 {
|
|
||||||
std::atomic_int_fast32_t _references {0}; // For intrusive_ptr.
|
|
||||||
CURLM* _multi = nullptr; ///< Initialized in `run`. Should not be used outside of it.
|
|
||||||
int _eventFd = 0; ///< Used to give the `curl_multi_wait` some work when there's no cURL descriptors and to wake it from `withCURLM`.
|
|
||||||
boost::lockfree::queue<CURL*, boost::lockfree::capacity<64>> _queue; ///< `CURL` handles waiting to be added to `CURL_MULTI`.
|
|
||||||
std::thread _thread;
|
|
||||||
std::atomic_bool _running {false}; /// True if the `_thread` is running.
|
|
||||||
|
|
||||||
using FreeOptions = CurlmInformationListener::FreeOptions;
|
|
||||||
|
|
||||||
friend inline void intrusive_ptr_add_ref (RunnerV2*);
|
|
||||||
friend inline void intrusive_ptr_release (RunnerV2*);
|
|
||||||
|
|
||||||
void run() noexcept {
|
|
||||||
try {
|
|
||||||
if (__builtin_expect (_references <= 0, 0)) GTHROW ("RunnerV2] Must be allocated with boost::intrusive_ptr!");
|
|
||||||
_running = true; // NB: _running only becomes true if we're in the intrusive_ptr. ^^
|
|
||||||
pthread_setname_np (pthread_self(), "Runner");
|
|
||||||
_multi = curl_multi_init(); if (__builtin_expect (_multi == nullptr, 0)) GTHROW ("!curl_multi_init");
|
|
||||||
_eventFd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK); // Used to pause `curl_multi_wait` when there's no other jobs.
|
|
||||||
if (__builtin_expect (_eventFd == -1, 0)) GTHROW (std::string ("eventfd: ") + ::strerror (errno));
|
|
||||||
while (__builtin_expect (_references > 0, 0)) {
|
|
||||||
// Reset the CURL_EVENT_FD value to 0, so that the `curl_multi_wait` can sleep.
|
|
||||||
if (__builtin_expect (_eventFd > 0, 1)) {eventfd_t count = 0; eventfd_read (_eventFd, &count);}
|
|
||||||
|
|
||||||
// Add the queued CURL handles to our CURLM.
|
|
||||||
CURL* easy = nullptr; while (_queue.pop (easy)) curl_multi_add_handle (_multi, easy);
|
|
||||||
|
|
||||||
// Run the cURL.
|
|
||||||
int runningHandles = 0;
|
|
||||||
CURLMcode rc = curl_multi_perform (_multi, &runningHandles); // http://curl.haxx.se/libcurl/c/curl_multi_perform.html
|
|
||||||
if (__builtin_expect (rc != CURLM_OK, 0)) BOOST_LOG_TRIVIAL (error) << "Runner] curl_multi_perform: " << curl_multi_strerror (rc);
|
|
||||||
|
|
||||||
// Process the finished handles.
|
|
||||||
for (;;) {
|
|
||||||
int messagesLeft = 0; CURLMsg* msg = curl_multi_info_read (_multi, &messagesLeft); if (msg) try {
|
|
||||||
CURL* curl = msg->easy_handle; CurlmInformationListener* listener = 0;
|
|
||||||
if (__builtin_expect (curl_easy_getinfo (curl, CURLINFO_PRIVATE, &listener) == CURLE_OK, 1)) {
|
|
||||||
using FOP = CurlmInformationListener::FreeOptions;
|
|
||||||
FOP fop = listener->information (msg, _multi);
|
|
||||||
if (fop & FOP::REMOVE_CURL_FROM_CURLM) curl_multi_remove_handle (_multi, curl);
|
|
||||||
if (fop & FOP::CURL_CLEANUP) curl_easy_cleanup (curl);
|
|
||||||
if (fop & FOP::DELETE_LISTENER) delete listener;
|
|
||||||
} else {
|
|
||||||
curl_multi_remove_handle (_multi, curl);
|
|
||||||
curl_easy_cleanup (curl);
|
|
||||||
}
|
|
||||||
} catch (const std::exception& ex) {BOOST_LOG_TRIVIAL (error) << "Runner] " << ex.what();}
|
|
||||||
if (messagesLeft == 0) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait on the cURL file descriptors.
|
|
||||||
int descriptors = 0;
|
|
||||||
curl_waitfd waitfd = {_eventFd, CURL_WAIT_POLLIN, 0};
|
|
||||||
eventfd_t eValue = 0; eventfd_read (_eventFd, &eValue); // Reset the curlEventFd value to zero.
|
|
||||||
rc = curl_multi_wait (_multi, &waitfd, 1, 100, &descriptors); // http://curl.haxx.se/libcurl/c/curl_multi_wait.html
|
|
||||||
if (__builtin_expect (rc != CURLM_OK, 0)) BOOST_LOG_TRIVIAL (error) << "Runner] curl_multi_wait: " << curl_multi_strerror (rc);
|
|
||||||
}
|
|
||||||
} catch (const std::exception& ex) {BOOST_LOG_TRIVIAL (error) << "Runner] " << ex.what();}
|
|
||||||
// Delayed destruction: when we're in intrusive_ptr (_running == true) but no longer referenced.
|
|
||||||
if (_running && _references == 0) delete this; // http://www.parashift.com/c++-faq-lite/delete-this.html
|
|
||||||
else _running = false;
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
RunnerV2() {
|
|
||||||
// Start a thread using CURLM in a thread-safe way (that is, from this single thread only).
|
|
||||||
// NB: Handles *can* be passed between threads: http://article.gmane.org/gmane.comp.web.curl.library/33188
|
|
||||||
_thread = std::thread (&RunnerV2::run, this);
|
|
||||||
}
|
|
||||||
~RunnerV2() {
|
|
||||||
_thread.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A singletone instance of the Runner used in order for different programes to reuse the same cURL thread.
|
|
||||||
static boost::intrusive_ptr<RunnerV2>& instance() {
|
|
||||||
static boost::intrusive_ptr<RunnerV2> INSTANCE (new RunnerV2());
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schedule a CURL handler to be executed in the cURL thread.
|
|
||||||
/// NB: If the handle have a `CURLOPT_PRIVATE` option then it MUST point to an instance of `CurlmInformationListener`.
|
|
||||||
void addToCURLM (CURL* easyHandle) {
|
|
||||||
if (__builtin_expect (!_queue.push (easyHandle), 0)) GTHROW ("Can't push CURL* into the queue.");
|
|
||||||
if (__builtin_expect (_eventFd > 0, 1)) eventfd_write (_eventFd, 1); // Will wake the `curl_multi_wait` up, in order to run the `curl_multi_add_handle`.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schedule a CURL handler to be executed in the cURL thread.
|
|
||||||
/// NB: `CURLOPT_PRIVATE` is overwritten with a pointer to `FunCurlmLisneter`.
|
|
||||||
void addToCURLM (CURL* easyHandle, std::function <void(CURLMsg*, CURLM*)>&& listener,
|
|
||||||
FreeOptions freeOptions = static_cast<FreeOptions> (FreeOptions::REMOVE_CURL_FROM_CURLM | FreeOptions::DELETE_LISTENER)) {
|
|
||||||
FunCurlmLisneter* funListener = new FunCurlmLisneter (std::move (listener), freeOptions); // Will be deleted by the Runner.
|
|
||||||
curl_easy_setopt (easyHandle, CURLOPT_PRIVATE, funListener); // Tells `addToCURLM` to call this listener later.
|
|
||||||
addToCURLM (easyHandle);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void intrusive_ptr_add_ref (RunnerV2* runner) {++ runner->_references;}
|
|
||||||
inline void intrusive_ptr_release (RunnerV2* runner) {if (-- runner->_references == 0 && !runner->_running) delete runner;}
|
|
||||||
|
|
||||||
/// Run CURLM requests and completion handlers, as well as other periodic jobs.
|
|
||||||
class Runner {
|
|
||||||
G_DEFINE_EXCEPTION (RunnerEx);
|
|
||||||
/// Free CURL during stack unwinding.
|
|
||||||
struct FreeCurl {
|
|
||||||
Runner* runner; CURL* curl;
|
|
||||||
FreeCurl (Runner* runner, CURL* curl): runner (runner), curl (curl) {}
|
|
||||||
~FreeCurl() {
|
|
||||||
runner->_handlers.erase (curl);
|
|
||||||
curl_multi_remove_handle (runner->_curlm, curl);
|
|
||||||
curl_easy_cleanup (curl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
struct JobInfo;
|
|
||||||
/// The job must return `true` if Runner is to continue invoking it.
|
|
||||||
typedef std::function<bool(JobInfo& jobInfo)> job_t;
|
|
||||||
struct JobInfo {
|
|
||||||
job_t job;
|
|
||||||
float pauseSec = 1.0f;
|
|
||||||
struct timespec ran = {0, 0};
|
|
||||||
};
|
|
||||||
protected:
|
|
||||||
typedef std::function<void(CURLMsg*)> handler_t;
|
|
||||||
typedef std::function<void(const char* error)> errlog_t;
|
|
||||||
std::shared_ptr<struct event_base> _evbase;
|
|
||||||
errlog_t _errlog;
|
|
||||||
std::recursive_mutex _mutex;
|
|
||||||
typedef std::unique_ptr<struct event, void(*)(struct event*)> event_t;
|
|
||||||
std::unordered_map<CURL*, std::pair<handler_t, event_t>> _handlers;
|
|
||||||
/// Functions to run periodically.
|
|
||||||
typedef std::unordered_map<gstring, JobInfo> jobs_map_t;
|
|
||||||
jobs_map_t _jobs;
|
|
||||||
CURLM* _curlm = nullptr;
|
|
||||||
struct event* _timer = nullptr;
|
|
||||||
|
|
||||||
/// Schedule a function to be run on the event loop. Useful to run all cURL methods on the single event loop thread.
|
|
||||||
template<typename F>
|
|
||||||
void doInEv (F fun, struct timeval after = {0, 0}) {
|
|
||||||
struct Dugout {F fun; struct event* timer; Dugout (F&& fun): fun (std::move (fun)), timer (nullptr) {}} *dugout = new Dugout (std::move (fun));
|
|
||||||
event_callback_fn cb = [](evutil_socket_t, short, void* dugout_)->void {
|
|
||||||
Dugout* dugout = static_cast<Dugout*> (dugout_);
|
|
||||||
event_free (dugout->timer); dugout->timer = nullptr;
|
|
||||||
F fun = std::move (dugout->fun); delete dugout;
|
|
||||||
fun();
|
|
||||||
};
|
|
||||||
dugout->timer = evtimer_new (_evbase.get(), cb, dugout);
|
|
||||||
evtimer_add (dugout->timer, &after);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool shouldRun (jobs_map_t::value_type& entry, const struct timespec& ct) {
|
|
||||||
JobInfo& jobInfo = entry.second;
|
|
||||||
if (jobInfo.pauseSec <= 0.f) return true; // Run always.
|
|
||||||
if (jobInfo.ran.tv_sec == 0) {jobInfo.ran = ct; return true;}
|
|
||||||
float delta = (float)(ct.tv_sec - jobInfo.ran.tv_sec);
|
|
||||||
delta += (float)(ct.tv_nsec - jobInfo.ran.tv_nsec) / 1000000000.0f;
|
|
||||||
if (delta >= jobInfo.pauseSec) {jobInfo.ran = ct; return true;}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used for debugging.
|
|
||||||
static uint64_t ms() {
|
|
||||||
return std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now().time_since_epoch()) .count();
|
|
||||||
}
|
|
||||||
/// Tells CURL to check its sockets.
|
|
||||||
void callCurlWithTimeout() {
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": callCurlWithTimeout" << std::endl;
|
|
||||||
int running_handles = 0;
|
|
||||||
CURLMcode rc = curl_multi_socket_action (_curlm, CURL_SOCKET_TIMEOUT, 0, &running_handles);
|
|
||||||
if (rc != CURLM_OK) {GSTRING_ON_STACK (err, 256) << "glim::Runner: curl_multi_socket_action: " << curl_multi_strerror (rc); _errlog (err.c_str());}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Should only be run when the _mutex is locked.
|
|
||||||
void checkForFinishedCurlJobs() {
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": checkForFinishedCurlJobs" << std::endl;
|
|
||||||
nextMessage:
|
|
||||||
int msgs_in_queue = 0;
|
|
||||||
CURLMsg* msg = curl_multi_info_read (_curlm, &msgs_in_queue);
|
|
||||||
if (msg) try {
|
|
||||||
auto curl = msg->easy_handle;
|
|
||||||
FreeCurl freeCurl (this, curl);
|
|
||||||
auto it = _handlers.find (curl);
|
|
||||||
if (it != _handlers.end()) it->second.first (msg);
|
|
||||||
if (msgs_in_queue > 0) goto nextMessage;
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
char eBuf[512]; gstring err (sizeof(eBuf), eBuf, false, 0);
|
|
||||||
err << "glim::Runner: handler: " << ex.what();
|
|
||||||
_errlog (err.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Will reset the timer unless there is a shorter timer already set.
|
|
||||||
void restartTimer (uint32_t nextInMicro = 100000) { // 100ms = 100000µs
|
|
||||||
struct timeval tv;
|
|
||||||
if (event_pending (_timer, EV_TIMEOUT, &tv) && !tv.tv_sec && tv.tv_usec < nextInMicro) return; // Already have a shorter timeout.
|
|
||||||
tv = {0, nextInMicro};
|
|
||||||
evtimer_add (_timer, &tv);
|
|
||||||
}
|
|
||||||
static void evTimerCB (evutil_socket_t, short, void* runner_) {
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": evTimerCB" << std::endl;
|
|
||||||
Runner* runner = (Runner*) runner_;
|
|
||||||
runner->callCurlWithTimeout();
|
|
||||||
runner->run();
|
|
||||||
}
|
|
||||||
/// event_callback_fn: There is an activity on a socket we are monitoring for CURL.
|
|
||||||
static void evSocketCB (evutil_socket_t sock, short events, void* runner_) {
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": evSocketCB; sock: " << sock << "; events: " << events << std::endl;
|
|
||||||
Runner* runner = (Runner*) runner_;
|
|
||||||
int ev_bitmask = (events & EV_READ ? CURL_CSELECT_IN : 0) | (events & EV_WRITE ? CURL_CSELECT_OUT : 0);
|
|
||||||
int running_handles = 0;
|
|
||||||
CURLMcode rc = curl_multi_socket_action (runner->_curlm, sock, ev_bitmask, &running_handles);
|
|
||||||
if (rc != CURLM_OK) {GSTRING_ON_STACK (err, 256) << "glim::Runner: curl_multi_socket_action: " << curl_multi_strerror (rc); runner->_errlog (err.c_str());}
|
|
||||||
}
|
|
||||||
static void deleteEvent (struct event* ev) {
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": deleteEvent: " << ev << std::endl;
|
|
||||||
event_del (ev); event_free (ev);
|
|
||||||
};
|
|
||||||
/// curl_socket_callback: CURL asks us to monitor the socket.
|
|
||||||
static int curlSocketCB (CURL* easy, curl_socket_t sock, int what, void* runner_, void* socketp) {
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": curlSocketCB; sock: " << sock << "; what: " << what;
|
|
||||||
//std::cout << " (" << (what == 0 ? "none" : what == 1 ? "in" : what == 2 ? "out" : what == 3 ? "inout" : what == 4 ? "remove" : "?") << ")" << std::endl;
|
|
||||||
Runner* runner = (Runner*) runner_;
|
|
||||||
std::lock_guard<std::recursive_mutex> lock (runner->_mutex);
|
|
||||||
if (what & CURL_POLL_REMOVE) {
|
|
||||||
auto it = runner->_handlers.find (easy); if (it != runner->_handlers.end()) it->second.second.reset();
|
|
||||||
// We can't run `checkForFinishedCurlJobs` from there or bad things would happen
|
|
||||||
// (`curl_multi_remove_handle` will be called while we are still in the `curl_multi_socket_action`),
|
|
||||||
// but we can schedule the check via the libevent timer.
|
|
||||||
runner->restartTimer (0);
|
|
||||||
} else {
|
|
||||||
auto it = runner->_handlers.find (easy); if (it != runner->_handlers.end() && !it->second.second) {
|
|
||||||
event_callback_fn cb = evSocketCB;
|
|
||||||
struct event* ev = event_new (runner->_evbase.get(), sock, EV_READ | EV_WRITE | EV_ET | EV_PERSIST, cb, runner);
|
|
||||||
event_add (ev, nullptr);
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": new event: " << ev << std::endl;
|
|
||||||
it->second.second = event_t (ev, deleteEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
/// curl_multi_timer_callback: Schedule a CURL timer event or if `timeout_ms` is 0 then run immediately.
|
|
||||||
static int curlTimerCB (CURLM* multi, long timeout_ms, void* runner_) {
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": curlTimerCB; timeout_ms: " << timeout_ms << std::endl;
|
|
||||||
if (timeout_ms == -1) return 0; // CURL tells us it doesn't need no timer.
|
|
||||||
Runner* runner = (Runner*) runner_;
|
|
||||||
if (timeout_ms == 0) { // CURL tells us it wants to run NOW.
|
|
||||||
runner->callCurlWithTimeout();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// CURL asks us to run it `timeout_ms` from now.
|
|
||||||
runner->restartTimer (std::min ((uint32_t) timeout_ms, (uint32_t) 100) * 1000); // We wait no more than 100ms.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
Runner (std::shared_ptr<struct event_base> evbase, errlog_t errlog): _evbase (evbase), _errlog (errlog) {
|
|
||||||
doInEv ([this]() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lock (_mutex);
|
|
||||||
_curlm = curl_multi_init(); if (!_curlm) GNTHROW (RunnerEx, "!curl_multi_init");
|
|
||||||
auto check = [this](CURLMcode rc) {if (rc != CURLM_OK) {curl_multi_cleanup (_curlm); GNTHROW (RunnerEx, "curl_multi_setopt: " + std::to_string (rc));}};
|
|
||||||
check (curl_multi_setopt (_curlm, CURLMOPT_SOCKETDATA, this));
|
|
||||||
curl_socket_callback socketCB = curlSocketCB; check (curl_multi_setopt (_curlm, CURLMOPT_SOCKETFUNCTION, socketCB));
|
|
||||||
check (curl_multi_setopt (_curlm, CURLMOPT_TIMERDATA, this));
|
|
||||||
curl_multi_timer_callback curlTimerCB_ = curlTimerCB; check (curl_multi_setopt (_curlm, CURLMOPT_TIMERFUNCTION, curlTimerCB_));
|
|
||||||
event_callback_fn evTimerCB_ = evTimerCB; _timer = evtimer_new (_evbase.get(), evTimerCB_, this);
|
|
||||||
restartTimer();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
~Runner() {
|
|
||||||
//std::cout << __LINE__ << ',' << ms() << ": ~Runner" << std::endl;
|
|
||||||
std::lock_guard<std::recursive_mutex> lock (_mutex);
|
|
||||||
if (_timer) {evtimer_del (_timer); event_free (_timer); _timer = nullptr;}
|
|
||||||
doInEv ([curlm = _curlm, handlers = std::move (_handlers)]() {
|
|
||||||
for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) {
|
|
||||||
curl_multi_remove_handle (curlm, it->first);
|
|
||||||
curl_easy_cleanup (it->first);
|
|
||||||
}
|
|
||||||
if (curlm) {curl_multi_cleanup (curlm);}
|
|
||||||
});
|
|
||||||
_curlm = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Turns HTTP Pipelining on (or off).
|
|
||||||
* See http://curl.haxx.se/libcurl/c/curl_multi_setopt.html#CURLMOPTPIPELINING */
|
|
||||||
Runner& pipeline (long enabled = 1) {
|
|
||||||
CURLMcode rc = curl_multi_setopt (_curlm, CURLMOPT_PIPELINING, enabled);
|
|
||||||
if (rc != CURLM_OK) GNTHROW (RunnerEx, "curl_multi_setopt: " + std::to_string (rc));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait for the operation to complete, then call the `handler`, then free the `curl`.
|
|
||||||
void multi (CURL* curl, handler_t handler) {
|
|
||||||
{ std::lock_guard<std::recursive_mutex> lock (_mutex);
|
|
||||||
_handlers.insert (std::make_pair (curl, std::make_pair (std::move (handler), event_t (nullptr, nullptr)))); }
|
|
||||||
doInEv ([this,curl]() {
|
|
||||||
curl_multi_add_handle (_curlm, curl);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/// Register a new job to be run on the thread loop.
|
|
||||||
JobInfo& job (const gstring& name) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lock (_mutex);
|
|
||||||
return _jobs[name];
|
|
||||||
}
|
|
||||||
/// Register a new job to be run on the thread loop.
|
|
||||||
void schedule (const gstring& name, float pauseSec, job_t job) {
|
|
||||||
struct timespec ct; if (pauseSec > 0.f) clock_gettime (CLOCK_MONOTONIC, &ct);
|
|
||||||
std::lock_guard<std::recursive_mutex> lock (_mutex);
|
|
||||||
JobInfo& jobInfo = _jobs[name];
|
|
||||||
jobInfo.job = job;
|
|
||||||
jobInfo.pauseSec = pauseSec;
|
|
||||||
if (pauseSec > 0.f) jobInfo.ran = ct; // If we need a pause then we also need to know when the job was scheduled.
|
|
||||||
}
|
|
||||||
/// Register a new job to be run on the thread loop.
|
|
||||||
void schedule (float pauseSec, job_t job) {
|
|
||||||
// Find a unique job name.
|
|
||||||
anotherName:
|
|
||||||
GSTRING_ON_STACK (name, 64) << "job" << rand();
|
|
||||||
if (_jobs.find (name) != _jobs.end()) goto anotherName;
|
|
||||||
schedule (name, pauseSec, std::move (job));
|
|
||||||
}
|
|
||||||
void removeJob (const gstring& name) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lock (_mutex);
|
|
||||||
_jobs.erase (name);
|
|
||||||
}
|
|
||||||
/// Invoked automatically from a libevent timer; can also be invoked manually.
|
|
||||||
void run() {
|
|
||||||
_mutex.lock();
|
|
||||||
checkForFinishedCurlJobs();
|
|
||||||
// Run non-CURL jobs. Copy jobs into a local array in order not to run them with the `_mutex` locked.
|
|
||||||
struct timespec ct; clock_gettime (CLOCK_MONOTONIC, &ct);
|
|
||||||
JobInfo jobs[_jobs.size()]; gstring jobNames[_jobs.size()]; int jn = -1; {
|
|
||||||
for (auto it = _jobs.begin(), end = _jobs.end(); it != end; ++it) if (shouldRun (*it, ct)) {
|
|
||||||
++jn; jobNames[jn] = it->first; jobs[jn] = it->second;
|
|
||||||
} }
|
|
||||||
_mutex.unlock();
|
|
||||||
|
|
||||||
for (; jn >= 0; --jn) try {
|
|
||||||
if (!jobs[jn].job (jobs[jn])) removeJob (jobNames[jn]);
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
char eBuf[512]; gstring err (sizeof(eBuf), eBuf, false, 0);
|
|
||||||
err << "glim::Runner: error in job " << jobNames[jn] << ": " << ex.what();
|
|
||||||
_errlog (err.c_str());
|
|
||||||
}
|
|
||||||
restartTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expose CURLM. Useful for curl_multi_setopt (http://curl.haxx.se/libcurl/c/curl_multi_setopt.html).
|
|
||||||
CURLM* curlm() const {return _curlm;}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace glim
|
|
||||||
|
|
||||||
#endif // _GLIM_RUNNER_INCLUDED
|
|
538
external/glim/sqlite.hpp
vendored
538
external/glim/sqlite.hpp
vendored
|
@ -1,538 +0,0 @@
|
||||||
#ifndef GLIM_SQLITE_HPP_
|
|
||||||
#define GLIM_SQLITE_HPP_
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A threaded interface to <a href="http://sqlite.org/">SQLite</a>.
|
|
||||||
* This file is a header-only library,
|
|
||||||
* whose sole dependencies should be standard STL and posix threading libraries.
|
|
||||||
* You can extract this file out of the "glim" library to include it separately in your project.
|
|
||||||
* @code
|
|
||||||
Copyright 2006-2012 Kozarezov Artem Aleksandrovich
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
* @endcode
|
|
||||||
* @file
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <string.h> // strerror
|
|
||||||
#include <sys/types.h> // stat
|
|
||||||
#include <sys/stat.h> // stat
|
|
||||||
#include <unistd.h> // stat
|
|
||||||
#include <errno.h> // stat
|
|
||||||
#include <stdio.h> // snprintf
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
namespace glim {
|
|
||||||
|
|
||||||
class SqliteSession;
|
|
||||||
class SqliteQuery;
|
|
||||||
|
|
||||||
struct SqliteEx: public std::runtime_error {
|
|
||||||
SqliteEx (const std::string& what): std::runtime_error (what) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database.
|
|
||||||
* According to sqlite3_open <a href="http://sqlite.org/capi3ref.html#sqlite3_open">documentation</a>,
|
|
||||||
* only the thread that opened the database can safely access it. This restriction was
|
|
||||||
* relaxed, as described in the <a href="http://www.sqlite.org/faq.html#q8">FAQ</a>
|
|
||||||
* (the "Is SQLite threadsafe?" question), so we can use the library from multiple
|
|
||||||
* threads, but only if no more than one thread at a time accesses the database.
|
|
||||||
* This restriction is, in fact, beneficial if the database is used from a single application:
|
|
||||||
* by restricting access to a sigle thread at a time, we effectively avoid all deadlock issues.\n
|
|
||||||
* This library goals are:\n
|
|
||||||
* \li to ensure that SQLite is used in a thread-safe way,
|
|
||||||
* \li to provide additional threaded quirks, such as delayed updates (not implemented).
|
|
||||||
*
|
|
||||||
* The library is targeted at SQLite setup which is \b not \c -DTHREADSAFE,
|
|
||||||
* since this is the default setup on UNIX architectures.\n
|
|
||||||
* \n
|
|
||||||
* This file is a header-only library,
|
|
||||||
* whose sole dependencies should be standard STL and posix threading libraries.
|
|
||||||
* You can extract this file out of the "glim" library to include it separately in your project.\n
|
|
||||||
* \n
|
|
||||||
* This library is targeted at UTF-8 API. There is no plans to support the UTF-16 API.\n
|
|
||||||
* \n
|
|
||||||
* See also:\n
|
|
||||||
* \li http://www.sqlite.org/cvstrac/fileview?f=sqlite/src/server.c\n
|
|
||||||
* for another way of handling multithreading with SQLite.
|
|
||||||
*/
|
|
||||||
class Sqlite {
|
|
||||||
/// No copying allowed.
|
|
||||||
Sqlite& operator = (const Sqlite& other) {return *this;}
|
|
||||||
/// No copying allowed.
|
|
||||||
Sqlite (const Sqlite& other) = delete;
|
|
||||||
friend class SqliteSession;
|
|
||||||
protected:
|
|
||||||
/// Filename the database was opened with; we need it to reopen the database on fork()s.
|
|
||||||
/// std::string is used to avoid memory allocation issues.
|
|
||||||
std::string filename;
|
|
||||||
::sqlite3* handler;
|
|
||||||
::pthread_mutex_t mutex;
|
|
||||||
public:
|
|
||||||
/// Flags for the Sqlite constructor.
|
|
||||||
enum Flags {
|
|
||||||
/**
|
|
||||||
* The file will be checked for existence.
|
|
||||||
* SqliteEx is thrown if the file is not accessible;
|
|
||||||
* format of the error description is "$filename: $strerror".\n
|
|
||||||
* Usage example: \code Sqlite db ("filename", Sqlite::existing); \endcode
|
|
||||||
*/
|
|
||||||
existing = 1
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Opens the database.
|
|
||||||
* @param filename Database filename (UTF-8).
|
|
||||||
* @param flags Optional. Currently there is the #existing flag.
|
|
||||||
* @throws SqliteEx Thrown if we can't open the database.
|
|
||||||
*/
|
|
||||||
Sqlite (std::string filename, int flags = 0) {
|
|
||||||
if (flags & existing) {
|
|
||||||
// Check if the file exists already.
|
|
||||||
struct stat st; if (stat (filename.c_str(), &st))
|
|
||||||
throw SqliteEx (filename + ": " + ::strerror(errno));
|
|
||||||
}
|
|
||||||
::pthread_mutex_init (&mutex, NULL);
|
|
||||||
this->filename = filename;
|
|
||||||
if (::sqlite3_open(filename.c_str(), &handler) != SQLITE_OK)
|
|
||||||
throw SqliteEx (std::string("sqlite3_open(") + filename + "): " + ::sqlite3_errmsg(handler));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Closes the database.
|
|
||||||
* @throws SqliteEx Thrown if we can't close the database.
|
|
||||||
*/
|
|
||||||
~Sqlite () {
|
|
||||||
::pthread_mutex_destroy (&mutex);
|
|
||||||
if (::sqlite3_close(handler) != SQLITE_OK)
|
|
||||||
throw SqliteEx (std::string ("sqlite3_close(): ") + ::sqlite3_errmsg(handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sqlite& exec (const char* query);
|
|
||||||
/**
|
|
||||||
* Invokes `exec` on `query.c_str()`.
|
|
||||||
* Example:\code
|
|
||||||
* glim::Sqlite sqlite (":memory:");
|
|
||||||
* for (std::string pv: {"page_size = 4096", "secure_delete = 1"}) sqlite->exec2 ("PRAGMA " + pv); \endcode
|
|
||||||
*/
|
|
||||||
template <typename StringLike> Sqlite& exec2 (StringLike query) {return exec (query.c_str());}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single thread session with Sqlite.
|
|
||||||
* Only a sigle thread at a time can have an SqliteSession,
|
|
||||||
* all other threads will wait, in the SqliteSession constructor,
|
|
||||||
* till the active session is either closed or destructed.
|
|
||||||
*/
|
|
||||||
class SqliteSession {
|
|
||||||
/// No copying allowed.
|
|
||||||
SqliteSession& operator = (const SqliteSession& other) {return *this;}
|
|
||||||
/// No copying allowed.
|
|
||||||
SqliteSession(SqliteSession& other): db (NULL) {}
|
|
||||||
protected:
|
|
||||||
Sqlite* db;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Locks the database.
|
|
||||||
* @throws SqliteEx if a mutex error occurs.
|
|
||||||
*/
|
|
||||||
SqliteSession (Sqlite* sqlite): db (sqlite) {
|
|
||||||
int err = ::pthread_mutex_lock (&(db->mutex));
|
|
||||||
if (err != 0) throw SqliteEx (std::string ("error locking the mutex: ") + ::strerror(err));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* A shorter way to construct query from the session.
|
|
||||||
* Usage example: \code ses.query(S("create table test (i integer)")).step() \endcode
|
|
||||||
* @see SqliteQuery#qstep
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
SqliteQuery query (T t);
|
|
||||||
/// Automatically unlocks the database.
|
|
||||||
/// @see close
|
|
||||||
~SqliteSession () {close();}
|
|
||||||
/**
|
|
||||||
* Unlock the database.
|
|
||||||
* It is safe to call this method multiple times.\n
|
|
||||||
* You must not use the session after it was closed.\n
|
|
||||||
* All resources allocated within this session must be released before the session is closed.
|
|
||||||
* @throws SqliteEx if a mutex error occurs.
|
|
||||||
*/
|
|
||||||
void close () {
|
|
||||||
if (db == NULL) return;
|
|
||||||
int err = ::pthread_mutex_unlock (&(db->mutex));
|
|
||||||
db = NULL;
|
|
||||||
if (err != 0) throw SqliteEx (std::string ("error unlocking the mutex: ") + ::strerror(err));
|
|
||||||
}
|
|
||||||
/// True if the \c close method has been already called on this SqliteSession.
|
|
||||||
bool isClosed () const {
|
|
||||||
return db == NULL;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This class can be used in place of the SQLite handler.
|
|
||||||
* Make sure you've released any resources thus manually acquired before this SqliteSession is closed.
|
|
||||||
* Usage example:
|
|
||||||
* @code
|
|
||||||
* glim::Sqlite db (":memory:");
|
|
||||||
* glim::SqliteSession ses (&db);
|
|
||||||
* sqlite3_exec (ses, "PRAGMA page_size = 4096;", NULL, NULL, NULL);
|
|
||||||
* @endcode
|
|
||||||
*/
|
|
||||||
operator ::sqlite3* () const {return db->handler;}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the given query, throwing SqliteEx on failure.\n
|
|
||||||
* Example:\code
|
|
||||||
* glim::Sqlite sqlite (":memory:");
|
|
||||||
* sqlite.exec ("PRAGMA page_size = 4096") .exec ("PRAGMA secure_delete = 1"); \endcode
|
|
||||||
*/
|
|
||||||
inline Sqlite& Sqlite::exec (const char* query) {
|
|
||||||
SqliteSession ses (this); // Maintains the locks.
|
|
||||||
char* errmsg = NULL; ::sqlite3_exec (handler, query, NULL, NULL, &errmsg);
|
|
||||||
if (errmsg) throw SqliteEx (std::string ("Sqlite::exec, error in query (") + query + "): " + errmsg);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps the sqlite3_stmt; will prepare it, bind values, query and finalize.
|
|
||||||
*/
|
|
||||||
class SqliteQuery {
|
|
||||||
protected:
|
|
||||||
::sqlite3_stmt* statement;
|
|
||||||
SqliteSession* session;
|
|
||||||
int bindCounter;
|
|
||||||
/// -1 if statement isn't DONE.
|
|
||||||
int mChanges;
|
|
||||||
void prepare (SqliteSession* session, char const* query, int queryLength) {
|
|
||||||
::sqlite3* handler = *session;
|
|
||||||
if (::sqlite3_prepare_v2 (handler, query, queryLength, &statement, NULL) != SQLITE_OK)
|
|
||||||
throw SqliteEx (std::string(query, queryLength) + ": " + ::sqlite3_errmsg(handler));
|
|
||||||
}
|
|
||||||
/** Shan't copy. */
|
|
||||||
SqliteQuery (const SqliteQuery& other) = delete;
|
|
||||||
public:
|
|
||||||
SqliteQuery (SqliteQuery&& rvalue) {
|
|
||||||
statement = rvalue.statement;
|
|
||||||
session = rvalue.session;
|
|
||||||
bindCounter = rvalue.bindCounter;
|
|
||||||
mChanges = rvalue.mChanges;
|
|
||||||
rvalue.statement = nullptr;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Prepares the query.
|
|
||||||
* @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
|
|
||||||
*/
|
|
||||||
SqliteQuery (SqliteSession* session, char const* query, int queryLength)
|
|
||||||
: statement (NULL), session (session), bindCounter (0), mChanges (-1) {
|
|
||||||
prepare (session, query, queryLength);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Prepares the query.
|
|
||||||
* @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
|
|
||||||
*/
|
|
||||||
SqliteQuery (SqliteSession* session, std::pair<char const*, int> query)
|
|
||||||
: statement (NULL), session (session), bindCounter (0), mChanges (-1) {
|
|
||||||
prepare (session, query.first, query.second);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Prepares the query.
|
|
||||||
* @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
|
|
||||||
*/
|
|
||||||
SqliteQuery (SqliteSession* session, std::string query)
|
|
||||||
: statement (NULL), session (session), bindCounter (0), mChanges (-1) {
|
|
||||||
prepare (session, query.c_str(), query.length());
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Release resources.
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_finalize
|
|
||||||
*/
|
|
||||||
~SqliteQuery () {
|
|
||||||
if (statement) ::sqlite3_finalize (statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call this (followed by the #step) if you need the query to be re-executed.
|
|
||||||
/// @see http://sqlite.org/capi3ref.html#sqlite3_reset
|
|
||||||
SqliteQuery& reset () {
|
|
||||||
bindCounter = 0;
|
|
||||||
mChanges = -1;
|
|
||||||
::sqlite3_reset (statement);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Synonym for #step.
|
|
||||||
bool next () {return step();}
|
|
||||||
/**
|
|
||||||
* Invokes sqlite3_step.
|
|
||||||
* @return \c true if there was a row fetched successfully, \c false if there is no more rows.
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_step
|
|
||||||
*/
|
|
||||||
bool step () {
|
|
||||||
if (mChanges >= 0) {mChanges = 0; return false;}
|
|
||||||
int ret = ::sqlite3_step (statement);
|
|
||||||
if (ret == SQLITE_ROW) return true;
|
|
||||||
if (ret == SQLITE_DONE) {
|
|
||||||
mChanges = ::sqlite3_changes (*session);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
throw SqliteEx (std::string(::sqlite3_errmsg(*session)));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Perform #step and throw an exception if #step has returned \c false.
|
|
||||||
* Usage example:
|
|
||||||
* \code (ses.query(S("select count(*) from test where idx = ?")) << 12345).qstep().intAt(1) \endcode
|
|
||||||
*/
|
|
||||||
SqliteQuery& qstep () {
|
|
||||||
if (!step())
|
|
||||||
throw SqliteEx (std::string("qstep: no rows returned / affected"));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Invokes a DML query and returns the number of rows affected.
|
|
||||||
* Example: \code
|
|
||||||
* int affected = (ses.query(S("update test set count = count + ? where id = ?")) << 1 << 9).ustep();
|
|
||||||
* \endcode
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_step
|
|
||||||
*/
|
|
||||||
int ustep () {
|
|
||||||
int ret = ::sqlite3_step (statement);
|
|
||||||
if (ret == SQLITE_DONE) {
|
|
||||||
mChanges = ::sqlite3_changes (*session);
|
|
||||||
return mChanges;
|
|
||||||
}
|
|
||||||
if (ret == SQLITE_ROW) return 0;
|
|
||||||
throw SqliteEx (std::string(::sqlite3_errmsg(*session)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of rows changed by the query.
|
|
||||||
* Providing the query was a DML (Data Modification Language),
|
|
||||||
* returns the number of rows updated.\n
|
|
||||||
* If the query wasn't a DML, returned value is undefined.\n
|
|
||||||
* -1 is returned if the query wasn't executed, or after #reset.\n
|
|
||||||
* Example: \code
|
|
||||||
* SqliteQuery query (&ses, S("update test set count = count + ? where id = ?"));
|
|
||||||
* query.bind (1, 1);
|
|
||||||
* query.bind (2, 9);
|
|
||||||
* query.step ();
|
|
||||||
* int affected = query.changes ();
|
|
||||||
* \endcode
|
|
||||||
* @see #ustep
|
|
||||||
*/
|
|
||||||
int changes () {return mChanges;}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The integer value of the given column.
|
|
||||||
* @param column 1-based.
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_column_text
|
|
||||||
*/
|
|
||||||
int intAt (int column) {
|
|
||||||
return ::sqlite3_column_int (statement, --column);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The integer value of the given column.
|
|
||||||
* @param column 1-based.
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_column_text
|
|
||||||
*/
|
|
||||||
sqlite3_int64 int64at (int column) {
|
|
||||||
return ::sqlite3_column_int64 (statement, --column);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The floating point number from the given column.
|
|
||||||
* @param column 1-based.
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_column_text
|
|
||||||
*/
|
|
||||||
double doubleAt (int column) {
|
|
||||||
return ::sqlite3_column_double (statement, --column);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the column as UTF-8 characters, which can be used until the next #step.
|
|
||||||
* @param column 1-based.
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_column_text
|
|
||||||
*/
|
|
||||||
std::pair<char const*, int> charsAt (int column) {
|
|
||||||
return std::pair<char const*, int> ((char const*) ::sqlite3_column_text (statement, column-1),
|
|
||||||
::sqlite3_column_bytes (statement, column-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the column as C++ string (UTF-8).
|
|
||||||
* @param column 1-based.
|
|
||||||
*/
|
|
||||||
std::string stringAt (int column) {
|
|
||||||
return std::string ((char const*) ::sqlite3_column_text (statement, column-1),
|
|
||||||
::sqlite3_column_bytes (statement, column-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the column.
|
|
||||||
* SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB or SQLITE_NULL.
|
|
||||||
* @param column 1-based.
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_column_text
|
|
||||||
*/
|
|
||||||
int typeAt (int column) {
|
|
||||||
return ::sqlite3_column_type (statement, --column);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds a value using one of the bind methods.
|
|
||||||
*/
|
|
||||||
template<typename T>
|
|
||||||
SqliteQuery& operator << (T value) {
|
|
||||||
return bind (++bindCounter, value);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Binds a value using the named parameter and one of the bind methods.
|
|
||||||
* @throws SqliteEx if the name could not be found.
|
|
||||||
* @see http://sqlite.org/capi3ref.html#sqlite3_bind_parameter_index
|
|
||||||
*/
|
|
||||||
template<typename T>
|
|
||||||
SqliteQuery& bind (char const* name, T value) {
|
|
||||||
int index = ::sqlite3_bind_parameter_index (statement, name);
|
|
||||||
if (index == 0)
|
|
||||||
throw SqliteEx (std::string ("No such parameter in the query: ") + name);
|
|
||||||
return bind (index, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind a string to the query.
|
|
||||||
* @param transient must be true, if lifetime of the string might be shorter than that of the query.
|
|
||||||
*/
|
|
||||||
SqliteQuery& bind (int index, const char* text, int length, bool transient = false) {
|
|
||||||
if (::sqlite3_bind_text (statement, index, text, length,
|
|
||||||
transient ? SQLITE_TRANSIENT : SQLITE_STATIC) != SQLITE_OK)
|
|
||||||
throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Bind a string to the query.
|
|
||||||
* @param transient must be true, if lifetime of the string might be shorter than that of the query.
|
|
||||||
*/
|
|
||||||
SqliteQuery& bind (int index, std::pair<const char*, int> text, bool transient = false) {
|
|
||||||
if (::sqlite3_bind_text (statement, index, text.first, text.second,
|
|
||||||
transient ? SQLITE_TRANSIENT : SQLITE_STATIC) != SQLITE_OK)
|
|
||||||
throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Bind a string to the query.
|
|
||||||
* @param transient must be true, if lifetime of the string might be shorter than that of the query.
|
|
||||||
*/
|
|
||||||
SqliteQuery& bind (int index, const std::string& text, bool transient = true) {
|
|
||||||
if (::sqlite3_bind_text (statement, index, text.data(), text.length(),
|
|
||||||
transient ? SQLITE_TRANSIENT : SQLITE_STATIC) != SQLITE_OK)
|
|
||||||
throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Bind an integer to the query.
|
|
||||||
*/
|
|
||||||
SqliteQuery& bind (int index, int value) {
|
|
||||||
if (::sqlite3_bind_int (statement, index, value) != SQLITE_OK)
|
|
||||||
throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Bind an 64-bit integer to the query.
|
|
||||||
*/
|
|
||||||
SqliteQuery& bind (int index, sqlite3_int64 value) {
|
|
||||||
if (::sqlite3_bind_int64 (statement, index, value) != SQLITE_OK)
|
|
||||||
throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version of SqliteQuery suitable for using SQLite in parallel with other processes.
|
|
||||||
* Will automatically handle the SQLITE_SCHEMA error
|
|
||||||
* and will automatically repeat attempts after SQLITE_BUSY,
|
|
||||||
* but it requires that the query string supplied
|
|
||||||
* is constant and available during the SqliteParQuery lifetime.
|
|
||||||
* Error messages, contained in exceptions, may differ from SqliteQuery by containing the query
|
|
||||||
* (for example, the #step method will throw "$query: $errmsg" instead of just "$errmsg").
|
|
||||||
*/
|
|
||||||
class SqliteParQuery: public SqliteQuery {
|
|
||||||
protected:
|
|
||||||
char const* query;
|
|
||||||
int queryLength;
|
|
||||||
int repeat;
|
|
||||||
int wait;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Prepares the query.
|
|
||||||
* @param repeat the number of times we try to repeat the query when SQLITE_BUSY is returned.
|
|
||||||
* @param wait how long, in milliseconds (1/1000 of a second) we are to wait before repeating.
|
|
||||||
* @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
|
|
||||||
*/
|
|
||||||
SqliteParQuery (SqliteSession* session, char const* query, int queryLength, int repeat = 90, int wait = 20)
|
|
||||||
: SqliteQuery (session, query, queryLength) {
|
|
||||||
this->query = query;
|
|
||||||
this->queryLength = queryLength;
|
|
||||||
this->repeat = repeat;
|
|
||||||
this->wait = wait;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Prepares the query.
|
|
||||||
* @param query the SQL query together with its length.
|
|
||||||
* @param repeat the number of times we try to repeat the query when SQLITE_BUSY is returned.
|
|
||||||
* @param wait how long, in milliseconds (1/1000 of a second) we are to wait before repeating.
|
|
||||||
* @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
|
|
||||||
*/
|
|
||||||
SqliteParQuery (SqliteSession* session, std::pair<char const*, int> query, int repeat = 90, int wait = 20)
|
|
||||||
: SqliteQuery (session, query) {
|
|
||||||
this->query = query.first;
|
|
||||||
this->queryLength = query.second;
|
|
||||||
this->repeat = repeat;
|
|
||||||
this->wait = wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool next () {return step();}
|
|
||||||
bool step () {
|
|
||||||
if (mChanges >= 0) {mChanges = 0; return false;}
|
|
||||||
repeat:
|
|
||||||
int ret = ::sqlite3_step (statement);
|
|
||||||
if (ret == SQLITE_ROW) return true;
|
|
||||||
if (ret == SQLITE_DONE) {
|
|
||||||
mChanges = ::sqlite3_changes (*session);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ret == SQLITE_SCHEMA) {
|
|
||||||
::sqlite3_stmt* old = statement;
|
|
||||||
prepare (session, query, queryLength);
|
|
||||||
::sqlite3_transfer_bindings(old, statement);
|
|
||||||
::sqlite3_finalize (old);
|
|
||||||
goto repeat;
|
|
||||||
}
|
|
||||||
if (ret == SQLITE_BUSY) for (int repeat = this->repeat; ret == SQLITE_BUSY && repeat >= 0; --repeat) {
|
|
||||||
//struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = wait * 1000000; // nan is 10^-9 of sec.
|
|
||||||
//while (::nanosleep (&ts, &ts) == EINTR);
|
|
||||||
::sqlite3_sleep (wait);
|
|
||||||
ret = ::sqlite3_step (statement);
|
|
||||||
}
|
|
||||||
throw SqliteEx (std::string(query, queryLength) + ::sqlite3_errmsg(*session));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
SqliteQuery SqliteSession::query (T t) {
|
|
||||||
return SqliteQuery (this, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
}; // namespace glim
|
|
||||||
|
|
||||||
#endif // GLIM_SQLITE_HPP_
|
|
89
external/glim/test_cbcoro.cc
vendored
89
external/glim/test_cbcoro.cc
vendored
|
@ -1,89 +0,0 @@
|
||||||
// http://en.wikipedia.org/wiki/Setcontext; man 3 makecontext; man 2 getcontext
|
|
||||||
// http://www.boost.org/doc/libs/1_53_0/libs/context/doc/html/index.html
|
|
||||||
// g++ -std=c++11 -O1 -Wall -g test_cbcoro.cc -pthread && ./a.out
|
|
||||||
|
|
||||||
#include <glim/exception.hpp>
|
|
||||||
#include <glim/NsecTimer.hpp>
|
|
||||||
|
|
||||||
#include "cbcoro.hpp"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h> // sleep
|
|
||||||
#include <string.h> // strerror
|
|
||||||
#include <errno.h>
|
|
||||||
#include <functional>
|
|
||||||
using std::function;
|
|
||||||
#include <thread>
|
|
||||||
#include <memory>
|
|
||||||
using std::shared_ptr; using std::make_shared;
|
|
||||||
#include <string>
|
|
||||||
using std::string; using std::to_string;
|
|
||||||
#include <iostream>
|
|
||||||
using std::cout; using std::endl;
|
|
||||||
|
|
||||||
/** A typical remote service with callback. */
|
|
||||||
void esDelete (int frople, std::function<void(int)> cb) {
|
|
||||||
std::thread th ([cb,frople]() {
|
|
||||||
cout << "esDelete: sleeping for a second" << endl;
|
|
||||||
std::this_thread::sleep_for (std::chrono::seconds (1));
|
|
||||||
cb (frople);
|
|
||||||
}); th.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RemoveFroples: public glim::CBCoro {
|
|
||||||
const char* _argument;
|
|
||||||
RemoveFroples (const char* argument): _argument (argument) {
|
|
||||||
cout << "RF: constructor" << endl;
|
|
||||||
}
|
|
||||||
virtual ~RemoveFroples() {puts ("~RemoveFroples");}
|
|
||||||
virtual void run() override {
|
|
||||||
for (int i = 1; i <= 4; ++i) {
|
|
||||||
cout << "RF: Removing frople " << i << "..." << endl;
|
|
||||||
int returnedFrople = 0;
|
|
||||||
yieldForCallback ([this,i,&returnedFrople]() {
|
|
||||||
if (i != 2) {
|
|
||||||
// Sometimes we use a callback.
|
|
||||||
esDelete (i, [this,&returnedFrople](int frople) {
|
|
||||||
cout << "RF,CB: frople " << frople << "." << endl;
|
|
||||||
returnedFrople = frople;
|
|
||||||
invokeFromCallback();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Sometimes we don't use a callback.
|
|
||||||
returnedFrople = 0;
|
|
||||||
invokeFromCallback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cout << "RF: Returned from callback; _returnTo is: " << (intptr_t) _returnTo << "; frople " << returnedFrople << endl;
|
|
||||||
}
|
|
||||||
cout << "RF: finish! _returnTo is: " << (intptr_t) _returnTo << endl;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
glim::cbCoro ([](glim::CBCoro* cbcoro) {
|
|
||||||
cout << "main: run1, thread " << std::this_thread::get_id() << endl; // Runs on the `main` thread.
|
|
||||||
cbcoro->yieldForCallback ([&]() {
|
|
||||||
std::thread callbackThread ([&]() {
|
|
||||||
std::this_thread::sleep_for (std::chrono::seconds (4));
|
|
||||||
cbcoro->invokeFromCallback();
|
|
||||||
}); callbackThread.detach();
|
|
||||||
});
|
|
||||||
cout << "main: run2, thread " << std::this_thread::get_id() << endl; // Runs on the `callbackThread`.
|
|
||||||
});
|
|
||||||
|
|
||||||
(new RemoveFroples ("argument"))->start();
|
|
||||||
cout << "main: returned from RemoveFroples" << endl;
|
|
||||||
|
|
||||||
glim::NsecTimer timer; const int ops = RUNNING_ON_VALGRIND ? 999 : 9999;
|
|
||||||
for (int i = 0; i < ops; ++i) glim::cbCoro ([](glim::CBCoro* cbcoro) {});
|
|
||||||
double speedEmpty = ops / timer.sec();
|
|
||||||
timer.restart();
|
|
||||||
for (int i = 0; i < ops; ++i) glim::cbCoro ([](glim::CBCoro* cbcoro) {cbcoro->yieldForCallback ([&]() {cbcoro->invokeFromCallback();});});
|
|
||||||
double speedImmediate = ops / timer.sec();
|
|
||||||
|
|
||||||
sleep (5);
|
|
||||||
cout << "speed: empty: " << speedEmpty << " o/s" << endl;
|
|
||||||
cout << "speed: immediate: " << speedImmediate << " o/s" << endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
73
external/glim/test_exception.cc
vendored
73
external/glim/test_exception.cc
vendored
|
@ -1,73 +0,0 @@
|
||||||
#define _GLIM_ALL_EXCEPTIONS_CODE
|
|
||||||
#include "exception.hpp"
|
|
||||||
#include <iostream>
|
|
||||||
#include <typeinfo>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
// NB: Controlling exceptions across shared object (.so) boundaries is tested separately in frople/src/test.cpp/testExceptionControl.
|
|
||||||
|
|
||||||
static void testThrowLine() {
|
|
||||||
int line = 0; std::string message; try {
|
|
||||||
line = __LINE__; GTHROW ("message");
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
message = ex.what();
|
|
||||||
}
|
|
||||||
//std::cout << message << ' ' << std::flush;
|
|
||||||
assert (message.size());
|
|
||||||
assert (std::string (message) .find (":" + std::to_string (line)) != std::string::npos);
|
|
||||||
|
|
||||||
line = 0; message.clear(); std::string name; try {
|
|
||||||
line = __LINE__; G_DEFINE_EXCEPTION (FooEx); GNTHROW (FooEx, "foo");
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
message = ex.what(); name = typeid (ex) .name();
|
|
||||||
}
|
|
||||||
//std::cout << "testThrowLine: " << message << ' ' << name << ' ' << std::flush;
|
|
||||||
assert (message.size());
|
|
||||||
assert (std::string (message) .find (":" + std::to_string (line)) != std::string::npos);
|
|
||||||
assert (name.find ("FooEx") != std::string::npos);
|
|
||||||
|
|
||||||
message.clear(); try {
|
|
||||||
glim::ExceptionControl plainWhat (glim::Exception::PLAIN_WHAT);
|
|
||||||
GTHROW ("bar");
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
message = ex.what();
|
|
||||||
}
|
|
||||||
assert (message == "bar");
|
|
||||||
assert (glim::Exception::options() == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testBacktrace() {
|
|
||||||
assert (glim::Exception::options() == 0);
|
|
||||||
glim::ExceptionControl captureTrace (glim::Exception::CAPTURE_TRACE);
|
|
||||||
assert (glim::Exception::options() != 0);
|
|
||||||
std::string message;
|
|
||||||
try {
|
|
||||||
GTHROW ("message");
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
message = ex.what();
|
|
||||||
}
|
|
||||||
//std::cout << "testBacktrace: " << message << std::endl;
|
|
||||||
if (message.find ("[at bin/test_exception") == std::string::npos && message.find ("[test_exception") == std::string::npos)
|
|
||||||
GTHROW ("No expected string in " + message);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testAllExceptionsHack() {
|
|
||||||
assert (glim::Exception::options() == 0);
|
|
||||||
std::string traceBuf;
|
|
||||||
glim::ExceptionHandler traceExceptions (glim::Exception::HANDLE_ALL | glim::Exception::RENDEZVOUS, glim::captureBacktrace, &traceBuf);
|
|
||||||
assert (glim::Exception::options() != 0);
|
|
||||||
try {
|
|
||||||
throw "catch me"; // Catched by `_GLIM_ALL_EXCEPTIONS_CODE` and handled with `glim::ExceptionControl::backtrace`.
|
|
||||||
} catch (const char* skip) {}
|
|
||||||
//std::cout << "testAllExceptionsHack: " << std::endl << traceBuf << std::endl;
|
|
||||||
assert (traceBuf.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
int main () {
|
|
||||||
std::cout << "Testing exception.hpp ... " << std::flush;
|
|
||||||
testThrowLine();
|
|
||||||
testBacktrace();
|
|
||||||
testAllExceptionsHack();
|
|
||||||
std::cout << "pass." << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
131
external/glim/test_gstring.cc
vendored
131
external/glim/test_gstring.cc
vendored
|
@ -1,131 +0,0 @@
|
||||||
#include "gstring.hpp"
|
|
||||||
using glim::gstring;
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <sstream>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
|
||||||
#include <boost/algorithm/string/trim.hpp>
|
|
||||||
#include <boost/algorithm/string/case_conv.hpp>
|
|
||||||
|
|
||||||
static void testIterators();
|
|
||||||
static void testBoost();
|
|
||||||
static void testStrftime();
|
|
||||||
|
|
||||||
int main () {
|
|
||||||
std::cout << "Testing gstring.hpp ... " << std::flush;
|
|
||||||
|
|
||||||
gstring gs;
|
|
||||||
if (gs.needsFreeing()) throw std::runtime_error ("Default gstring needsFreeing");
|
|
||||||
if (gs.capacity() != 1) throw std::runtime_error ("Default gstring capacity is not 1");
|
|
||||||
char buf16[16];
|
|
||||||
gstring gs16 (sizeof (buf16), buf16, false, 0);
|
|
||||||
if (gs16.capacity() != 16) throw std::runtime_error ("gs16 capacity != 16");
|
|
||||||
if (gs16.size() != 0) throw std::runtime_error ("gs16 size != 0");
|
|
||||||
gstring gsFree (17, NULL, true, 0);
|
|
||||||
if (!gsFree.needsFreeing()) throw std::runtime_error ("!needsFreeing");
|
|
||||||
if (gsFree.capacity() != 16) throw std::runtime_error ("gsFree capacity != 16");
|
|
||||||
if (gsFree.size() != 0) throw std::runtime_error ("gsFree size != 0");
|
|
||||||
gstring gsRO (0, NULL, false, 0);
|
|
||||||
if (gsRO.needsFreeing()) throw std::runtime_error ("needsFreeing");
|
|
||||||
if (gsRO.capacity() != 1) throw std::runtime_error ("gsRO capacity != 1");
|
|
||||||
if (gsRO.size() != 0) throw std::runtime_error ("gsRO size != 0");
|
|
||||||
char buf32[32];
|
|
||||||
gstring gs32 (sizeof (buf32), buf32, false, 0);
|
|
||||||
if (gs32.capacity() != 32) throw std::runtime_error ("capacity != 32");
|
|
||||||
if (gs32.size() != 0) throw std::runtime_error ("gs32 size != 0");
|
|
||||||
const gstring foo = C2GSTRING ("foo");
|
|
||||||
if (foo.needsFreeing()) throw std::runtime_error ("foo needsFreeing");
|
|
||||||
if (foo != "foo") throw std::runtime_error ("foo != foo");
|
|
||||||
if (foo.size() != 3) throw std::runtime_error ("foo not 3");
|
|
||||||
std::ostringstream oss; oss << gs16 << gsFree << gsRO << gs32 << foo;
|
|
||||||
if (oss.str() != "foo") throw std::runtime_error ("oss foo != foo");
|
|
||||||
glim::gstring_stream gss (gs16); std::ostream gsos (&gss);
|
|
||||||
gsos << "bar" << std::flush;
|
|
||||||
if (gs16 != "bar") throw std::runtime_error ("gs16 != bar");
|
|
||||||
gsos << "beer" << std::flush;
|
|
||||||
if (gs16 != "barbeer") throw std::runtime_error ("gs16 != barbeer");
|
|
||||||
gsos << "123456789" << std::flush;
|
|
||||||
if (gs16 != "barbeer123456789") throw std::runtime_error ("gs16 != barbeer123456789");
|
|
||||||
if (gs16.capacity() != 16) throw std::runtime_error ("gs16 != 16");
|
|
||||||
gsos << '0' << std::flush;
|
|
||||||
if (gs16 != "barbeer1234567890") throw std::runtime_error ("gs16 != barbeer1234567890");
|
|
||||||
if (gs16.capacity() != 32) throw std::runtime_error ("gs16 != 32");
|
|
||||||
|
|
||||||
gstring gsb; std::string str ("abc");
|
|
||||||
gsb << 'a' << 1 << 2LL << str;
|
|
||||||
std::string ns ("1:3,"); std::istringstream nsi (ns);
|
|
||||||
gsb.readNetstring (nsi);
|
|
||||||
if (gsb != "a12abc3") throw std::runtime_error ("gsb != a12abc3");
|
|
||||||
if (strcmp (gsb.c_str(), "a12abc3") != 0) throw std::runtime_error ("strcmp ! 0");
|
|
||||||
|
|
||||||
gsb.clear().appendNetstring ("foo") .appendNetstring ("bar");
|
|
||||||
if (gsb != "3:foo,3:bar,") throw std::runtime_error ("gsb != 3:foo,3:bar,");
|
|
||||||
uint32_t pos = 0;
|
|
||||||
if (gsb.netstringAt (pos, &pos) != "foo" || gsb.netstringAt (pos, &pos) != "bar" || pos != gsb.length())
|
|
||||||
throw std::runtime_error ("gsb !netstringAt");
|
|
||||||
|
|
||||||
gs32.clear() << 12345 << ',';
|
|
||||||
if (gs32.intAt (0, &pos) != 12345 || pos != 5) throw std::runtime_error ("gsb !12345");
|
|
||||||
if (gs32.intAt (1, &pos) != 2345 || pos != 5) throw std::runtime_error ("gsb !2345");
|
|
||||||
if (gs32.intAt (5, &pos) != 0 || pos != 5) throw std::runtime_error ("gsb !0");
|
|
||||||
|
|
||||||
if ((gs32.clear() << 123).erase (0) != "23") throw std::runtime_error ("!23");
|
|
||||||
if ((gs32.clear() << 123).erase (1) != "13") throw std::runtime_error ("!13");
|
|
||||||
if ((gs32.clear() << 123).erase (2) != "12") throw std::runtime_error ("!12");
|
|
||||||
|
|
||||||
std::unordered_map<glim::gstring, int> map;
|
|
||||||
map[glim::gstring ("foo")] = 1;
|
|
||||||
glim::gstring bar ("bar");
|
|
||||||
map[bar] = 1;
|
|
||||||
map[glim::gstring ("sum")] = map[glim::gstring ("foo")] + map[glim::gstring ("bar")];
|
|
||||||
if (map[glim::gstring ("sum")] != 2) throw std::runtime_error ("sum != 2");
|
|
||||||
map.clear();
|
|
||||||
|
|
||||||
gstring gs1 ("foo"); gstring gs2 ("bar");
|
|
||||||
gs1 = gstring (gs2 << "_"); // Copying in order to malloc length() bytes.
|
|
||||||
if (gs1 != "bar_") throw std::runtime_error ("!bar_");
|
|
||||||
if (gs1.capacity() != 1) throw std::runtime_error ("bar_ != 4");
|
|
||||||
|
|
||||||
testIterators();
|
|
||||||
testBoost();
|
|
||||||
testStrftime();
|
|
||||||
|
|
||||||
std::cout << "pass." << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testIterators() {
|
|
||||||
gstring foo (C2GSTRING ("foo"));
|
|
||||||
gstring buf; for (auto it = foo.begin(), end = foo.end(); it != end; ++it) buf << *it;
|
|
||||||
assert (buf == "foo");
|
|
||||||
assert (boost::starts_with (foo, "f") && boost::ends_with (foo, "oo"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testBoost() {
|
|
||||||
gstring str (" foo\t\r\n");
|
|
||||||
boost::trim (str);
|
|
||||||
assert (str == "foo");
|
|
||||||
|
|
||||||
gstring up ("FOO"); boost::to_lower (up);
|
|
||||||
assert (up == "foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testStrftime() {
|
|
||||||
time_t tim = time(0); struct tm ltime; memset (<ime, 0, sizeof ltime); if (!localtime_r (&tim, <ime)) GTHROW ("!localtime_r");
|
|
||||||
GSTRING_ON_STACK (t1, 8); assert (t1.capacity() == 8);
|
|
||||||
t1.appendTime ("", <ime); assert (t1 == "");
|
|
||||||
t1.appendTime ("foo %a, %d %b %Y %T %z bar", <ime);
|
|
||||||
assert (t1.capacity() > 8); // Capacity increased to account for the large string.
|
|
||||||
assert (boost::starts_with (t1, "foo "));
|
|
||||||
assert (boost::ends_with (t1, " bar"));
|
|
||||||
|
|
||||||
GSTRING_ON_STACK (t2, 8); assert (t2.capacity() == 8);
|
|
||||||
t2.appendTime ("%H", <ime);
|
|
||||||
assert (t2.capacity() == 8); // 8 is big enought, isn't it?
|
|
||||||
assert (t2.intAt (0) == ltime.tm_hour); // NB: intAt is safe here because strftime adds an uncounted null-terminator.
|
|
||||||
}
|
|
165
external/glim/test_ldb.cc
vendored
165
external/glim/test_ldb.cc
vendored
|
@ -1,165 +0,0 @@
|
||||||
#include "ldb.hpp"
|
|
||||||
using glim::Ldb;
|
|
||||||
using glim::gstring;
|
|
||||||
#include <iostream>
|
|
||||||
using std::cout; using std::flush; using std::endl;
|
|
||||||
#include <assert.h>
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
|
|
||||||
void test1 (Ldb& ldb) {
|
|
||||||
ldb.put (std::string ("foo_"), std::string ("bar"));
|
|
||||||
ldb.put ((uint32_t) 123, 1);
|
|
||||||
ldb.put ((uint32_t) 123, 2);
|
|
||||||
ldb.put (C2GSTRING ("foo"), 3);
|
|
||||||
ldb.put (C2GSTRING ("foo"), 4);
|
|
||||||
ldb.put (C2GSTRING ("gsk"), C2GSTRING ("gsv"));
|
|
||||||
std::string ts; int ti; gstring tgs;
|
|
||||||
auto fail = [](std::string msg) {throw std::runtime_error ("assertion failed: " + msg);};
|
|
||||||
if (!ldb.get (std::string ("foo_"), ts) || ts != "bar") fail ("!foo_=bar");
|
|
||||||
if (!ldb.get ((uint32_t) 123, ti) || ti != 2) fail ("!123=2");
|
|
||||||
if (!ldb.get (C2GSTRING ("foo"), ti) || ti != 4) fail ("!foo=4");
|
|
||||||
if (!ldb.get (C2GSTRING ("gsk"), tgs) || tgs != "gsv") fail ("!gsk=gsv");
|
|
||||||
|
|
||||||
// Test range-based for.
|
|
||||||
int count = 0; bool haveGskGsv = false;
|
|
||||||
for (auto&& entry: ldb) {
|
|
||||||
if (!entry._lit->Valid()) fail ("!entry");
|
|
||||||
if (entry.keyView() == "gsk") {
|
|
||||||
if (entry.getKey<gstring>() != "gsk") fail ("getKey(gsk)!=gsk");
|
|
||||||
if (entry.getValue<gstring>() != "gsv") fail ("getValue(gsk)!=gsv");
|
|
||||||
haveGskGsv = true;
|
|
||||||
}
|
|
||||||
++count;}
|
|
||||||
if (count != 4) fail ("count!=4"); // foo_=bar, 123=2, foo=4, gsk=gsv
|
|
||||||
if (!haveGskGsv) fail ("!haveGskGsv");
|
|
||||||
|
|
||||||
ldb.del ((uint32_t) 123); if (ldb.get ((uint32_t) 123, ti)) fail ("123");
|
|
||||||
ldb.del (C2GSTRING ("foo")); if (ldb.get (C2GSTRING ("foo"), ti)) fail ("foo");
|
|
||||||
ldb.del (std::string ("foo_"));
|
|
||||||
|
|
||||||
{ // We've erased "123" and "foo", the only key left is "gsk" (gsk=gsv), let's test the iterator boundaries on this small dataset.
|
|
||||||
auto&& it = ldb.begin();
|
|
||||||
if (it->getKey<gstring>() != "gsk") fail ("first key !gsk " + it->keyView().str());
|
|
||||||
if (!(++it).end()) fail ("++it != end");
|
|
||||||
if ((--it)->getKey<gstring>() != "gsk") fail ("can't go back to gsk");
|
|
||||||
if (!(--it).end()) fail ("--it != end");
|
|
||||||
if ((++it)->getKey<gstring>() != "gsk") fail ("can't go forward to gsk");
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: index trigger example
|
|
||||||
// struct SimpleIndexTrigger: public Trigger { // Uses key space partitioning (cf. http://stackoverflow.com/a/12503799/257568)
|
|
||||||
// const char* _name; Ldb _indexDb;
|
|
||||||
// SimpleIndexTrigger (Ldb& ldb, const char* name = "index"): _name (name), _indexDb (ldb._env, name) {}
|
|
||||||
// gstring triggerName() {return gstring (0, (void*) _name, false, strlen (_name), true);}
|
|
||||||
// void add (Ldb& ldb, void* key, gstring& kbytes, void* value, gstring& vbytes, Transaction& txn) {
|
|
||||||
// MDB_val mkey = {vbytes.size(), (void*) vbytes.data()};
|
|
||||||
// MDB_val mvalue = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
// int rc = ::ldb_put (txn.get(), _indexDb._dbi, &mkey, &mvalue, 0);
|
|
||||||
// if (rc) GNTHROW (LdbEx, std::string ("index, ldb_put: ") + ::strerror (rc));
|
|
||||||
// }
|
|
||||||
// void erase (Ldb& ldb, void* ekey, gstring& kbytes, Transaction& txn) {
|
|
||||||
// // Get all the values and remove them from the index.
|
|
||||||
// MDB_cursor* cur = 0; int rc = ::ldb_cursor_open (txn.get(), ldb._dbi, &cur);
|
|
||||||
// if (rc) GNTHROW (LdbEx, std::string ("index, erase, ldb_cursor_open: ") + ::strerror (rc));
|
|
||||||
// std::unique_ptr<MDB_cursor, void(*)(MDB_cursor*)> curHolder (cur, ::ldb_cursor_close);
|
|
||||||
// MDB_val mkey = {kbytes.size(), (void*) kbytes.data()}, val = {0, 0};
|
|
||||||
// rc = ::ldb_cursor_get (cur, &mkey, &val, ::MDB_SET_KEY); if (rc == MDB_NOTFOUND) return;
|
|
||||||
// if (rc) GNTHROW (LdbEx, std::string ("index, erase, ldb_cursor_get: ") + ::strerror (rc));
|
|
||||||
// rc = ::ldb_del (txn.get(), _indexDb._dbi, &val, &mkey);
|
|
||||||
// if (rc && rc != MDB_NOTFOUND) GNTHROW (LdbEx, std::string ("index, erase, ldb_del: ") + ::strerror (rc));
|
|
||||||
// for (;;) {
|
|
||||||
// rc = ::ldb_cursor_get (cur, &mkey, &val, ::MDB_NEXT_DUP); if (rc == MDB_NOTFOUND) return;
|
|
||||||
// if (rc) GNTHROW (LdbEx, std::string ("index, erase, ldb_cursor_get: ") + ::strerror (rc));
|
|
||||||
// rc = ::ldb_del (txn.get(), _indexDb._dbi, &val, &mkey);
|
|
||||||
// if (rc && rc != MDB_NOTFOUND) GNTHROW (LdbEx, std::string ("index, erase, ldb_del: ") + ::strerror (rc));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// void eraseKV (Ldb& ldb, void* key, gstring& kbytes, void* value, gstring& vbytes, Transaction& txn) {
|
|
||||||
// MDB_val mkey = {vbytes.size(), (void*) vbytes.data()};
|
|
||||||
// MDB_val mvalue = {kbytes.size(), (void*) kbytes.data()};
|
|
||||||
// int rc = ::ldb_del (txn.get(), _indexDb._dbi, &mkey, &mvalue);
|
|
||||||
// if (rc && rc != MDB_NOTFOUND) GNTHROW (LdbEx, std::string ("index, ldb_del: ") + ::strerror (rc));
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// auto indexTrigger = std::make_shared<SimpleIndexTrigger> (ldb); ldb.setTrigger (indexTrigger); auto& indexDb = indexTrigger->_indexDb;
|
|
||||||
// ldb.erase (C2GSTRING ("gsk")); // NB: "gsk" wasn't indexed here. `IndexTrigger.erase` should handle this gracefully.
|
|
||||||
|
|
||||||
// Add indexed.
|
|
||||||
// ldb.put (C2GSTRING ("ik"), C2GSTRING ("iv1"));
|
|
||||||
// ldb.put (C2GSTRING ("ik"), string ("iv2"));
|
|
||||||
// ldb.put (C2GSTRING ("ik"), 3);
|
|
||||||
// Check the index.
|
|
||||||
// gstring ik;
|
|
||||||
// if (!indexDb.first (C2GSTRING ("iv1"), ik) || ik != "ik") fail ("!iv1=ik");
|
|
||||||
// if (!indexDb.first (string ("iv2"), ik) || ik != "ik") fail ("!iv2=ik");
|
|
||||||
// if (!indexDb.first (3, ik) || ik != "ik") fail ("!iv3=ik");
|
|
||||||
|
|
||||||
// Remove indexed.
|
|
||||||
// ldb.eraseKV (C2GSTRING ("ik"), string ("iv2"));
|
|
||||||
// Check the index.
|
|
||||||
// if (!indexDb.first (C2GSTRING ("iv1"), ik) || ik != "ik") fail ("!iv1=ik");
|
|
||||||
// if (indexDb.first (string ("iv2"), ik)) fail ("iv2=ik");
|
|
||||||
// if (!indexDb.first (3, ik) || ik != "ik") fail ("!iv3=ik");
|
|
||||||
|
|
||||||
// Remove indexed.
|
|
||||||
// ldb.erase (C2GSTRING ("ik"));
|
|
||||||
// Check the index.
|
|
||||||
// if (indexDb.first (C2GSTRING ("iv1"), ik)) fail ("iv1");
|
|
||||||
// if (indexDb.first (3, ik)) fail ("iv3");
|
|
||||||
// Check the data.
|
|
||||||
// if (ldb.first (C2GSTRING ("ik"), ik)) fail ("ik");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testStartsWith (Ldb& ldb) {
|
|
||||||
// Using `gstring`s because the Boost Serialization encoding for CStrings is not prefix-friendly.
|
|
||||||
ldb.put (C2GSTRING ("01"), ""); ldb.put (C2GSTRING ("02"), "");
|
|
||||||
ldb.put (C2GSTRING ("11"), "");
|
|
||||||
ldb.put (C2GSTRING ("21"), ""); ldb.put (C2GSTRING ("222"), ""); ldb.put (C2GSTRING ("2"), "");
|
|
||||||
|
|
||||||
auto range = ldb.startsWith (C2GSTRING ("0")); auto it = range.begin();
|
|
||||||
assert (it->keyView() == "01"); assert (it != range.end());
|
|
||||||
assert ((++it)->keyView() == "02"); assert (it != range.end());
|
|
||||||
assert ((++it)->keyView().empty()); assert (it == range.end());
|
|
||||||
assert ((--it)->keyView() == "02"); assert (it != range.end());
|
|
||||||
assert ((--it)->keyView() == "01"); assert (it != range.end());
|
|
||||||
assert ((--it)->keyView().empty()); assert (it == range.end());
|
|
||||||
// `it` and `range.begin` point to the same `leveldb::Iterator`.
|
|
||||||
assert (range.begin()._entry->_lit == it._entry->_lit);
|
|
||||||
assert (!range.begin()._entry->_valid); assert (range.begin()->keyView().empty());
|
|
||||||
|
|
||||||
range = ldb.startsWith (C2GSTRING ("0")); it = range.end();
|
|
||||||
assert (it.end() && it->keyView().empty()); assert (it != range.begin());
|
|
||||||
assert ((--it)->keyView() == "02"); assert (it != range.begin());
|
|
||||||
assert ((--it)->keyView() == "01"); assert (it == range.begin());
|
|
||||||
assert ((--it)->keyView().empty()); assert (it != range.begin());
|
|
||||||
|
|
||||||
int8_t count = 0; for (auto& en: ldb.startsWith (C2GSTRING ("1"))) {en.keyView(); ++count;} assert (count == 1);
|
|
||||||
count = 0; for (auto& en: ldb.startsWith (C2GSTRING ("2"))) {en.keyView(); ++count;} assert (count == 3);
|
|
||||||
count = 0; for (auto& en: ldb.startsWith (C2GSTRING ("-"))) {en.keyView(); ++count;} assert (count == 0);
|
|
||||||
count = 0; for (auto& en: ldb.startsWith (C2GSTRING (""))) {en.keyView(); ++count;} assert (count == 6);
|
|
||||||
|
|
||||||
assert (ldb.startsWith (C2GSTRING ("-")) .empty());
|
|
||||||
|
|
||||||
count = 0; for (auto& en: boost::make_iterator_range (ldb.end().seek ("1"), ldb.end().seek ("2"))) {en.keyView(); ++count;} assert (count == 1);
|
|
||||||
count = 0; for (auto& en: boost::make_iterator_range (ldb.end().seek ("2"), ldb.end().seek ("3"))) {en.keyView(); ++count;} assert (count == 3);
|
|
||||||
count = 0; for (auto& en: ldb.range (C2GSTRING ("1"), C2GSTRING ("2"))) {en.keyView(); ++count;} assert (count == 1);
|
|
||||||
|
|
||||||
{ auto range = ldb.range (C2GSTRING ("0"), C2GSTRING ("1")); // 01 and 02, but not 11.
|
|
||||||
count = 0; for (auto& en: range) {en.keyView(); ++count;} assert (count == 2); }
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
cout << "Testing ldb.hpp ... " << flush;
|
|
||||||
boost::filesystem::remove_all ("/dev/shm/ldbTest");
|
|
||||||
|
|
||||||
Ldb ldb ("/dev/shm/ldbTest");
|
|
||||||
test1 (ldb);
|
|
||||||
|
|
||||||
for (auto& en: ldb) ldb.del (en.keyView());
|
|
||||||
testStartsWith (ldb);
|
|
||||||
|
|
||||||
ldb._db.reset(); // Close.
|
|
||||||
boost::filesystem::remove_all ("/dev/shm/ldbTest");
|
|
||||||
cout << "pass." << endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
74
external/glim/test_runner.cc
vendored
74
external/glim/test_runner.cc
vendored
|
@ -1,74 +0,0 @@
|
||||||
#include "runner.hpp"
|
|
||||||
#include "curl.hpp"
|
|
||||||
#include <future>
|
|
||||||
#include <thread>
|
|
||||||
#include <iostream>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
static void testRunner() {
|
|
||||||
using glim::RunnerV2;
|
|
||||||
auto runner = RunnerV2::instance();
|
|
||||||
|
|
||||||
struct Dugout: public glim::CurlmInformationListener {
|
|
||||||
glim::Curl _curl;
|
|
||||||
std::promise<std::string> _promise;
|
|
||||||
void schedule (RunnerV2* runner) {
|
|
||||||
_curl.http ("http://glim.ru/", 2);
|
|
||||||
curl_easy_setopt (_curl._curl, CURLOPT_PRIVATE, this); // Tells `addToCURLM` to call this listener later.
|
|
||||||
runner->addToCURLM (_curl._curl);
|
|
||||||
}
|
|
||||||
virtual FreeOptions information (CURLMsg* msg, CURLM* curlm) override {
|
|
||||||
_promise.set_value (_curl.str());
|
|
||||||
return static_cast<FreeOptions> (REMOVE_CURL_FROM_CURLM | DELETE_LISTENER);
|
|
||||||
}
|
|
||||||
virtual ~Dugout() {}
|
|
||||||
};
|
|
||||||
{ Dugout* dugout = new Dugout();
|
|
||||||
auto future = dugout->_promise.get_future();
|
|
||||||
dugout->schedule (runner.get());
|
|
||||||
if (future.get().find ("<html") == std::string::npos) GTHROW ("!html"); }
|
|
||||||
|
|
||||||
auto curl = std::make_shared<glim::Curl>(); curl->http ("http://glim.ru/", 2);
|
|
||||||
auto promise = std::make_shared<std::promise<std::string>>(); auto future = promise->get_future();
|
|
||||||
runner->addToCURLM (curl->_curl, [curl,promise](CURLMsg*, CURLM*) {promise->set_value (curl->str());});
|
|
||||||
if (future.get().find ("<html") == std::string::npos) GTHROW ("!html");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void oldTest() {
|
|
||||||
std::shared_ptr<struct event_base> evbase (event_base_new(), event_base_free);
|
|
||||||
glim::Runner runner (evbase, [](const char* error) {std::cerr << error << std::endl;});
|
|
||||||
|
|
||||||
auto scheduledJobFired = std::make_shared<bool> (false);
|
|
||||||
runner.schedule (0.f, [=](glim::Runner::JobInfo&)->bool {*scheduledJobFired = true; return false;});
|
|
||||||
|
|
||||||
auto curl = std::make_shared<glim::Curl> (false); curl->http ("http://glim.ru/env.cgi?pause=50", 5);
|
|
||||||
auto curlDebug = std::make_shared<std::string>(); curl->debugListenerF ([curlDebug](const char* bytes, size_t size) {curlDebug->append (bytes, size);});
|
|
||||||
volatile bool ran = false;
|
|
||||||
runner.multi (curl->_curl, [curl,&ran,evbase,curlDebug](CURLMsg* msg) {
|
|
||||||
std::cout << " status: " << curl->status();
|
|
||||||
if (curl->status() == 200) std::cout << " ip: " << curl->gstr().view (0, std::max (curl->gstr().find ("\n"), 0));
|
|
||||||
if (curlDebug->find ("GET /env.cgi") == std::string::npos) std::cerr << " No headers in debug? " << *curlDebug << std::endl;
|
|
||||||
ran = true;
|
|
||||||
event_base_loopbreak (evbase.get());
|
|
||||||
});
|
|
||||||
//struct timeval tv {1, 0}; event_base_loopexit (evbase.get(), &tv); // Exit the loop in a sec.
|
|
||||||
event_base_dispatch (evbase.get());
|
|
||||||
if (!ran) GTHROW ("!ran");
|
|
||||||
if (!*scheduledJobFired) GTHROW ("!scheduledJobFired");
|
|
||||||
|
|
||||||
std::cout << " pass." << std::endl;
|
|
||||||
//waiting: "was introduced in Libevent 2.1.1-alpha"//libevent_global_shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main () {
|
|
||||||
std::cout << "Testing runner.hpp ..." << std::flush; try {
|
|
||||||
|
|
||||||
testRunner();
|
|
||||||
oldTest();
|
|
||||||
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
std::cerr << " exception: " << ex.what() << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
18
external/glim/test_sqlite.cc
vendored
18
external/glim/test_sqlite.cc
vendored
|
@ -1,18 +0,0 @@
|
||||||
|
|
||||||
#include "sqlite.hpp"
|
|
||||||
#define S(cstr) (std::pair<char const*, int> (cstr, sizeof (cstr) - 1))
|
|
||||||
#include <assert.h>
|
|
||||||
#include <iostream>
|
|
||||||
using namespace glim;
|
|
||||||
|
|
||||||
int main () {
|
|
||||||
std::cout << "Testing sqlite.hpp ... " << std::flush;
|
|
||||||
Sqlite sqlite (":memory:");
|
|
||||||
SqliteSession sqs (&sqlite);
|
|
||||||
assert (sqs.query ("CREATE TABLE test (t TEXT, i INTEGER)") .ustep() == 0);
|
|
||||||
assert (sqs.query ("INSERT INTO test VALUES (?, ?)") .bind (1, S("foo")) .bind (2, 27) .ustep() == 1);
|
|
||||||
assert (sqs.query ("SELECT t FROM test") .qstep() .stringAt (1) == "foo");
|
|
||||||
assert (sqs.query ("SELECT i FROM test") .qstep() .intAt (1) == 27);
|
|
||||||
std::cout << "pass." << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Reference in a new issue