File: | state.cpp |
Warning: | line 212, column 10 Although the value stored to 'r' is used in the enclosing expression, the value is never actually read from 'r' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* |
2 | * Copyright (C) 2020 Tino Didriksen <mail@tinodidriksen.com> |
3 | * |
4 | * This program is free software: you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation, either version 3 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "state.hpp" |
19 | #include "shared.hpp" |
20 | #include "base64.hpp" |
21 | #include <xxhash.h> |
22 | #include <sqlite3.h> |
23 | #include <array> |
24 | #include <map> |
25 | #include <stdexcept> |
26 | |
27 | // SQLite use is completely contained in this file and hidden from the rest of the codebase |
28 | // Only begin() and commit() hint at there being a database for storage, but they would also be useful for other storage backends |
29 | |
30 | namespace Transfuse { |
31 | |
32 | inline auto sqlite3_exec(sqlite3* db, const char* sql) { |
33 | return ::sqlite3_exec(db, sql, nullptr, nullptr, nullptr); |
34 | } |
35 | |
36 | struct sqlite3_stmt_h { |
37 | sqlite3_stmt*& operator()() { |
38 | return s; |
39 | } |
40 | |
41 | operator sqlite3_stmt*() { |
42 | return s; |
43 | } |
44 | |
45 | void reset() { |
46 | if (s) { |
47 | sqlite3_reset(s); |
48 | } |
49 | } |
50 | |
51 | void clear() { |
52 | if (s) { |
53 | sqlite3_finalize(s); |
54 | } |
55 | s = nullptr; |
56 | } |
57 | |
58 | ~sqlite3_stmt_h() { |
59 | clear(); |
60 | } |
61 | |
62 | protected: |
63 | sqlite3_stmt* s = nullptr; |
64 | }; |
65 | |
66 | enum Stmt { |
67 | info_sel, |
68 | info_ins, |
69 | style_ins, |
70 | style_sel, |
71 | num_stmts |
72 | }; |
73 | |
74 | struct State::impl { |
75 | std::string name; |
76 | std::string format; |
77 | std::string stream; |
78 | std::string tmp_s; |
79 | |
80 | std::map<std::string, std::map<std::string, std::pair<std::string, std::string>>> styles; |
81 | |
82 | sqlite3* db = nullptr; |
83 | std::array<sqlite3_stmt_h, num_stmts> stmts; |
84 | |
85 | auto& stm(Stmt s) { |
86 | return stmts[s]; |
87 | } |
88 | |
89 | ~impl() { |
90 | for (auto& stm : stmts) { |
91 | stm.clear(); |
92 | } |
93 | sqlite3_close(db); |
94 | } |
95 | }; |
96 | |
97 | State::State(fs::path tmpdir, bool ro) |
98 | : tmpdir(tmpdir) |
99 | , s(std::make_unique<impl>()) |
100 | { |
101 | if (sqlite3_initialize() != SQLITE_OK0) { |
102 | throw std::runtime_error("sqlite3_initialize() errored"); |
103 | } |
104 | |
105 | int flags = ro ? (SQLITE_OPEN_READONLY0x00000001) : (SQLITE_OPEN_READWRITE0x00000002 | SQLITE_OPEN_CREATE0x00000004); |
106 | if (sqlite3_open_v2((tmpdir / "state.sqlite3").string().c_str(), &s->db, flags, nullptr) != SQLITE_OK0) { |
107 | throw std::runtime_error(concat("sqlite3_open_v2() error: ", sqlite3_errmsg(s->db))); |
108 | } |
109 | |
110 | // All the write operations and writing prepared statements |
111 | if (!ro) { |
112 | if (sqlite3_exec(s->db, "CREATE TABLE IF NOT EXISTS info (key TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)") != SQLITE_OK0) { |
113 | throw std::runtime_error(concat("sqlite3 error while creating info table: ", sqlite3_errmsg(s->db))); |
114 | } |
115 | |
116 | if (sqlite3_exec(s->db, "CREATE TABLE IF NOT EXISTS styles (tag TEXT NOT NULL, hash TEXT NOT NULL, otag TEXT NOT NULL, ctag TEXT NOT NULL, PRIMARY KEY (tag, hash))") != SQLITE_OK0) { |
117 | throw std::runtime_error(concat("sqlite3 error while creating inlines table: ", sqlite3_errmsg(s->db))); |
118 | } |
119 | |
120 | if (sqlite3_prepare_v2(s->db, "INSERT OR REPLACE INTO info (key, value) VALUES (:key, :value)", -1, &s->stm(info_ins)(), nullptr) != SQLITE_OK0) { |
121 | throw std::runtime_error(concat("sqlite3 error preparing insert into info table: ", sqlite3_errmsg(s->db))); |
122 | } |
123 | |
124 | if (sqlite3_prepare_v2(s->db, "INSERT OR REPLACE INTO styles (tag, hash, otag, ctag) VALUES (:tag, :hash, :otag, :ctag)", -1, &s->stm(style_ins)(), nullptr) != SQLITE_OK0) { |
125 | throw std::runtime_error(concat("sqlite3 error preparing insert into styles table: ", sqlite3_errmsg(s->db))); |
126 | } |
127 | } |
128 | |
129 | // All the reading prepared statements |
130 | if (sqlite3_prepare_v2(s->db, "SELECT value FROM info WHERE key = :key", -1, &s->stm(info_sel)(), nullptr) != SQLITE_OK0) { |
131 | throw std::runtime_error(concat("sqlite3 error preparing select from info table: ", sqlite3_errmsg(s->db))); |
132 | } |
133 | |
134 | if (sqlite3_prepare_v2(s->db, "SELECT tag, hash, otag, ctag FROM styles", -1, &s->stm(style_sel)(), nullptr) != SQLITE_OK0) { |
135 | throw std::runtime_error(concat("sqlite3 error preparing select from styles table: ", sqlite3_errmsg(s->db))); |
136 | } |
137 | } |
138 | |
139 | State::~State() { |
140 | } |
141 | |
142 | void State::begin() { |
143 | if (sqlite3_exec(s->db, "BEGIN") != SQLITE_OK0) { |
144 | throw std::runtime_error(concat("sqlite3 error while beginning transaction: ", sqlite3_errmsg(s->db))); |
145 | } |
146 | } |
147 | |
148 | void State::commit() { |
149 | if (sqlite3_exec(s->db, "COMMIT") != SQLITE_OK0) { |
150 | throw std::runtime_error(concat("sqlite3 error while committing transaction: ", sqlite3_errmsg(s->db))); |
151 | } |
152 | } |
153 | |
154 | void State::name(std::string_view val) { |
155 | info("name", val); |
156 | s->name = val; |
157 | } |
158 | |
159 | std::string_view State::name() { |
160 | if (s->name.empty()) { |
161 | s->name = info("name"); |
162 | } |
163 | return s->name; |
164 | } |
165 | |
166 | void State::format(std::string_view val) { |
167 | info("format", val); |
168 | s->format = val; |
169 | } |
170 | |
171 | std::string_view State::format() { |
172 | if (s->format.empty()) { |
173 | s->format = info("format"); |
174 | } |
175 | return s->format; |
176 | } |
177 | |
178 | void State::stream(std::string_view val) { |
179 | info("stream", val); |
180 | s->stream = val; |
181 | } |
182 | |
183 | std::string_view State::stream() { |
184 | if (s->stream.empty()) { |
185 | s->stream = info("stream"); |
186 | } |
187 | return s->stream; |
188 | } |
189 | |
190 | void State::info(std::string_view key, std::string_view val) { |
191 | s->stm(info_ins).reset(); |
192 | if (sqlite3_bind_text(s->stm(info_ins), 1, key.data(), SI(key.size()), SQLITE_STATIC((sqlite3_destructor_type)0)) != SQLITE_OK0) { |
193 | throw std::runtime_error(concat("sqlite3 error trying to bind text for key: ", sqlite3_errmsg(s->db))); |
194 | } |
195 | if (sqlite3_bind_text(s->stm(info_ins), 2, val.data(), SI(val.size()), SQLITE_STATIC((sqlite3_destructor_type)0)) != SQLITE_OK0) { |
196 | throw std::runtime_error(concat("sqlite3 error trying to bind text for value: ", sqlite3_errmsg(s->db))); |
197 | } |
198 | if (sqlite3_step(s->stm(info_ins)) != SQLITE_DONE101) { |
199 | throw std::runtime_error(concat("sqlite3 error inserting into info table: ", sqlite3_errmsg(s->db))); |
200 | } |
201 | } |
202 | |
203 | std::string State::info(std::string_view key) { |
204 | std::string rv; |
205 | |
206 | s->stm(info_sel).reset(); |
207 | if (sqlite3_bind_text(s->stm(info_sel), 1, key.data(), SI(key.size()), SQLITE_STATIC((sqlite3_destructor_type)0)) != SQLITE_OK0) { |
208 | throw std::runtime_error(concat("sqlite3 error trying to bind text for key: ", sqlite3_errmsg(s->db))); |
209 | } |
210 | |
211 | int r = 0; |
212 | while ((r = sqlite3_step(s->stm(info_sel))) == SQLITE_ROW100) { |
Although the value stored to 'r' is used in the enclosing expression, the value is never actually read from 'r' | |
213 | rv = reinterpret_cast<const char*>(sqlite3_column_text(s->stm(info_sel), 0)); |
214 | } |
215 | |
216 | return rv; |
217 | } |
218 | |
219 | xmlChar_view State::style(xmlChar_view _name, xmlChar_view _otag, xmlChar_view _ctag) { |
220 | auto name = x2s(_name); |
221 | auto otag = x2s(_otag); |
222 | auto ctag = x2s(_ctag); |
223 | |
224 | // Make sure that empty opening or closing tag still causes a difference |
225 | s->tmp_s.assign(otag.begin(), otag.end()); |
226 | s->tmp_s += TFI_HASH_SEP"\xee\x80\x90"; |
227 | s->tmp_s += ctag; |
228 | auto h32 = XXH32(s->tmp_s.data(), s->tmp_s.size(), 0); |
229 | base64_url(s->tmp_s, h32); |
230 | |
231 | s->stm(style_ins).reset(); |
232 | if (sqlite3_bind_text(s->stm(style_ins), 1, name.data(), SI(name.size()), SQLITE_STATIC((sqlite3_destructor_type)0)) != SQLITE_OK0) { |
233 | throw std::runtime_error(concat("sqlite3 error trying to bind text for tag: ", sqlite3_errmsg(s->db))); |
234 | } |
235 | if (sqlite3_bind_text(s->stm(style_ins), 2, s->tmp_s.data(), SI(s->tmp_s.size()), SQLITE_STATIC((sqlite3_destructor_type)0)) != SQLITE_OK0) { |
236 | throw std::runtime_error(concat("sqlite3 error trying to bind text for hash: ", sqlite3_errmsg(s->db))); |
237 | } |
238 | if (sqlite3_bind_text(s->stm(style_ins), 3, otag.data(), SI(otag.size()), SQLITE_STATIC((sqlite3_destructor_type)0)) != SQLITE_OK0) { |
239 | throw std::runtime_error(concat("sqlite3 error trying to bind text for otag: ", sqlite3_errmsg(s->db))); |
240 | } |
241 | if (sqlite3_bind_text(s->stm(style_ins), 4, ctag.data(), SI(ctag.size()), SQLITE_STATIC((sqlite3_destructor_type)0)) != SQLITE_OK0) { |
242 | throw std::runtime_error(concat("sqlite3 error trying to bind text for ctag: ", sqlite3_errmsg(s->db))); |
243 | } |
244 | if (sqlite3_step(s->stm(style_ins)) != SQLITE_DONE101) { |
245 | throw std::runtime_error(concat("sqlite3 error inserting into styles table: ", sqlite3_errmsg(s->db))); |
246 | } |
247 | |
248 | return s2x(s->tmp_s); |
249 | } |
250 | |
251 | std::pair<std::string_view, std::string_view> State::style(std::string_view tag, std::string_view hash) { |
252 | if (s->styles.empty()) { |
253 | std::string t; |
254 | std::string h; |
255 | std::string o; |
256 | std::string c; |
257 | s->stm(style_sel).reset(); |
258 | int r = 0; |
259 | while ((r = sqlite3_step(s->stm(style_sel))) == SQLITE_ROW100) { |
260 | t = reinterpret_cast<const char*>(sqlite3_column_text(s->stm(style_sel), 0)); |
261 | h = reinterpret_cast<const char*>(sqlite3_column_text(s->stm(style_sel), 1)); |
262 | o = reinterpret_cast<const char*>(sqlite3_column_text(s->stm(style_sel), 2)); |
263 | c = reinterpret_cast<const char*>(sqlite3_column_text(s->stm(style_sel), 3)); |
264 | s->styles[t][h] = std::make_pair(o, c); |
265 | } |
266 | } |
267 | |
268 | s->tmp_s.assign(tag.begin(), tag.end()); |
269 | auto t = s->styles.find(s->tmp_s); |
270 | if (t == s->styles.end()) { |
271 | return {}; |
272 | } |
273 | |
274 | s->tmp_s.assign(hash.begin(), hash.end()); |
275 | auto oc = t->second.find(s->tmp_s); |
276 | if (oc == t->second.end()) { |
277 | return {}; |
278 | } |
279 | return { oc->second.first, oc->second.second }; |
280 | } |
281 | |
282 | } |