summaryrefslogtreecommitdiffstats
path: root/common/storage_lmdb.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'common/storage_lmdb.cpp')
-rw-r--r--common/storage_lmdb.cpp282
1 files changed, 282 insertions, 0 deletions
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
new file mode 100644
index 0000000..283205f
--- /dev/null
+++ b/common/storage_lmdb.cpp
@@ -0,0 +1,282 @@
1#include "storage.h"
2
3#include <iostream>
4
5#include <QAtomicInt>
6#include <QDebug>
7#include <QDir>
8#include <QReadWriteLock>
9#include <QString>
10#include <QTime>
11
12#include <lmdb.h>
13
14class Storage::Private
15{
16public:
17 Private(const QString &s, const QString &name);
18 ~Private();
19
20 QString storageRoot;
21 QString name;
22
23 MDB_dbi dbi;
24 MDB_env *env;
25 MDB_txn *transaction;
26 bool readTransaction;
27 bool firstOpen;
28};
29
30Storage::Private::Private(const QString &s, const QString &n)
31 : transaction(0),
32 readTransaction(false),
33 firstOpen(true),
34 storageRoot(s),
35 name(n)
36{
37 QDir dir;
38 dir.mkdir(storageRoot);
39
40 //create file
41 if (mdb_env_create(&env)) {
42 // TODO: handle error
43 } else {
44 int rc = mdb_env_open(env, storageRoot.toStdString().data(), 0, 0664);
45
46 if (rc) {
47 std::cerr << "mdb_env_open: " << rc << " " << mdb_strerror(rc) << std::endl;
48 mdb_env_close(env);
49 env = 0;
50 } else {
51 const size_t dbSize = 10485760 * 100; //10MB * 100
52 mdb_env_set_mapsize(env, dbSize);
53 }
54 }
55}
56
57Storage::Private::~Private()
58{
59 if (transaction) {
60 mdb_txn_abort(transaction);
61 }
62
63 // it is still there and still unused, so we can shut it down
64 mdb_dbi_close(env, dbi);
65 mdb_env_close(env);
66}
67
68Storage::Storage(const QString &storageRoot, const QString &name)
69 : d(new Private(storageRoot, name))
70{
71}
72
73Storage::~Storage()
74{
75 delete d;
76}
77
78bool Storage::isInTransaction() const
79{
80 return d->transaction;
81}
82
83bool Storage::startTransaction(TransactionType type)
84{
85 if (!d->env) {
86 return false;
87 }
88
89 bool requestedRead = type == ReadOnly;
90 if (d->transaction && (!d->readTransaction || requestedRead)) {
91 return true;
92 }
93
94 if (d->transaction) {
95 // we are about to turn a read transaction into a writable one
96 abortTransaction();
97 }
98
99 if (d->firstOpen && requestedRead) {
100 //A write transaction is at least required the first time
101 mdb_txn_begin(d->env, nullptr, 0, &d->transaction);
102 //Open the database
103 //With this we could open multiple named databases if we wanted to
104 mdb_dbi_open(d->transaction, nullptr, 0, &d->dbi);
105 mdb_txn_abort(d->transaction);
106 }
107
108 int rc;
109 rc = mdb_txn_begin(d->env, NULL, requestedRead ? MDB_RDONLY : 0, &d->transaction);
110 if (!rc) {
111 rc = mdb_dbi_open(d->transaction, NULL, 0, &d->dbi);
112 }
113
114 d->firstOpen = false;
115 return !rc;
116}
117
118bool Storage::commitTransaction()
119{
120 if (!d->env) {
121 return false;
122 }
123
124 if (!d->transaction) {
125 return false;
126 }
127
128 int rc;
129 rc = mdb_txn_commit(d->transaction);
130 d->transaction = 0;
131
132 if (rc) {
133 std::cerr << "mdb_txn_commit: " << rc << " " << mdb_strerror(rc) << std::endl;
134 }
135
136 return !rc;
137}
138
139void Storage::abortTransaction()
140{
141 if (!d->env || !d->transaction) {
142 return;
143 }
144
145 mdb_txn_abort(d->transaction);
146 d->transaction = 0;
147}
148
149bool Storage::write(const char *key, size_t keySize, const char *value, size_t valueSize)
150{
151 write(std::string(key, keySize), std::string(value, valueSize));
152}
153
154bool Storage::write(const std::string &sKey, const std::string &sValue)
155{
156 if (!d->env) {
157 return false;
158 }
159
160 const bool implicitTransaction = !d->transaction || d->readTransaction;
161 if (implicitTransaction) {
162 // TODO: if this fails, still try the write below?
163 if (!startTransaction()) {
164 return false;
165 }
166 }
167
168 int rc;
169 MDB_val key, data;
170 key.mv_size = sKey.size();
171 key.mv_data = (void*)sKey.data();
172 data.mv_size = sValue.size();
173 data.mv_data = (void*)sValue.data();
174 rc = mdb_put(d->transaction, d->dbi, &key, &data, 0);
175
176 if (rc) {
177 std::cerr << "mdb_put: " << rc << " " << mdb_strerror(rc) << std::endl;
178 }
179
180 if (implicitTransaction) {
181 if (rc) {
182 abortTransaction();
183 } else {
184 rc = commitTransaction();
185 }
186 }
187
188 return !rc;
189}
190
191bool Storage::read(const std::string &sKey, const std::function<void(const std::string &value)> &resultHandler)
192{
193 return read(sKey,
194 [&](void *ptr, int size) {
195 const std::string resultValue(static_cast<char*>(ptr), size);
196 resultHandler(resultValue);
197 });
198// std::cout << "key: " << resultKey << " data: " << resultValue << std::endl;
199}
200
201bool Storage::read(const std::string &sKey, const std::function<void(void *ptr, int size)> &resultHandler)
202{
203 if (!d->env) {
204 return false;
205 }
206
207 int rc;
208 MDB_val key;
209 MDB_val data;
210 MDB_cursor *cursor;
211
212 key.mv_size = sKey.size();
213 key.mv_data = (void*)sKey.data();
214
215 const bool implicitTransaction = !d->transaction;
216 if (implicitTransaction) {
217 // TODO: if this fails, still try the write below?
218 if (!startTransaction(ReadOnly)) {
219 return false;
220 }
221 }
222
223 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor);
224 if (rc) {
225 std::cerr << "mdb_cursor_get: " << rc << " " << mdb_strerror(rc) << std::endl;
226 return false;
227 }
228
229 if (sKey.empty()) {
230 std::cout << "Iterating over all values of store!" << std::endl;
231 rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
232 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
233 resultHandler(key.mv_data, data.mv_size);
234 }
235
236 //We never find the last value
237 if (rc == MDB_NOTFOUND) {
238 rc = 0;
239 }
240 } else {
241 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_SET)) == 0) {
242 resultHandler(data.mv_data, data.mv_size);
243 } else {
244 std::cout << "couldn't find value " << sKey << " " << std::endl;
245 }
246 }
247
248 mdb_cursor_close(cursor);
249
250 if (rc) {
251 std::cerr << "mdb_cursor_get: " << rc << " " << mdb_strerror(rc) << std::endl;
252 return false;
253 }
254
255 /**
256 we don't abort the transaction since we need it for reading the values
257 if (implicitTransaction) {
258 abortTransaction();
259 }
260 */
261 return true;
262}
263
264qint64 Storage::diskUsage() const
265{
266 QFileInfo info(d->storageRoot + "/data.mdb");
267 return info.size();
268}
269
270void Storage::removeFromDisk() const
271{
272 QDir dir(d->path);
273 dir.remove("data.mdb");
274 dir.remove("lock.mdb");
275}
276
277void Storage::removeFromDisk() const
278{
279 QDir dir(d->storageRoot);
280 dir.remove("data.mdb");
281 dir.remove("lock.mdb");
282}