UNPKG

13.6 kBtext/x-cView Raw
1#include <cstring>
2#include <napi.h>
3#include "macros.h"
4#include "database.h"
5#include "backup.h"
6
7using namespace node_sqlite3;
8
9Napi::Object Backup::Init(Napi::Env env, Napi::Object exports) {
10 Napi::HandleScope scope(env);
11
12 // declare napi_default_method here as it is only available in Node v14.12.0+
13 auto napi_default_method = static_cast<napi_property_attributes>(napi_writable | napi_configurable);
14
15 auto t = DefineClass(env, "Backup", {
16 InstanceMethod("step", &Backup::Step, napi_default_method),
17 InstanceMethod("finish", &Backup::Finish, napi_default_method),
18 InstanceAccessor("idle", &Backup::IdleGetter, nullptr),
19 InstanceAccessor("completed", &Backup::CompletedGetter, nullptr),
20 InstanceAccessor("failed", &Backup::FailedGetter, nullptr),
21 InstanceAccessor("remaining", &Backup::RemainingGetter, nullptr),
22 InstanceAccessor("pageCount", &Backup::PageCountGetter, nullptr),
23 InstanceAccessor("retryErrors", &Backup::RetryErrorGetter, &Backup::RetryErrorSetter),
24 });
25
26 exports.Set("Backup", t);
27 return exports;
28}
29
30void Backup::Process() {
31 if (finished && !queue.empty()) {
32 return CleanQueue();
33 }
34
35 while (inited && !locked && !queue.empty()) {
36 auto call = std::move(queue.front());
37 queue.pop();
38
39 call->callback(call->baton);
40 }
41}
42
43void Backup::Schedule(Work_Callback callback, Baton* baton) {
44 if (finished) {
45 queue.emplace(new Call(callback, baton));
46 CleanQueue();
47 }
48 else if (!inited || locked || !queue.empty()) {
49 queue.emplace(new Call(callback, baton));
50 }
51 else {
52 callback(baton);
53 }
54}
55
56template <class T> void Backup::Error(T* baton) {
57 auto env = baton->backup->Env();
58 Napi::HandleScope scope(env);
59
60 Backup* backup = baton->backup;
61 // Fail hard on logic errors.
62 assert(backup->status != 0);
63 EXCEPTION(Napi::String::New(env, backup->message), backup->status, exception);
64
65 Napi::Function cb = baton->callback.Value();
66
67 if (!cb.IsEmpty() && cb.IsFunction()) {
68 Napi::Value argv[] = { exception };
69 TRY_CATCH_CALL(backup->Value(), cb, 1, argv);
70 }
71 else {
72 Napi::Value argv[] = { Napi::String::New(env, "error"), exception };
73 EMIT_EVENT(backup->Value(), 2, argv);
74 }
75}
76
77void Backup::CleanQueue() {
78 auto env = this->Env();
79 Napi::HandleScope scope(env);
80
81 if (inited && !queue.empty()) {
82 // This backup has already been initialized and is now finished.
83 // Fire error for all remaining items in the queue.
84 EXCEPTION(Napi::String::New(env, "Backup is already finished"), SQLITE_MISUSE, exception);
85 Napi::Value argv[] = { exception };
86 bool called = false;
87
88 // Clear out the queue so that this object can get GC'ed.
89 while (!queue.empty()) {
90 auto call = std::move(queue.front());
91 queue.pop();
92
93 std::unique_ptr<Baton> baton(call->baton);
94 Napi::Function cb = baton->callback.Value();
95
96 if (inited && !cb.IsEmpty() &&
97 cb.IsFunction()) {
98 TRY_CATCH_CALL(Value(), cb, 1, argv);
99 called = true;
100 }
101 }
102
103 // When we couldn't call a callback function, emit an error on the
104 // Backup object.
105 if (!called) {
106 Napi::Value info[] = { Napi::String::New(env, "error"), exception };
107 EMIT_EVENT(Value(), 2, info);
108 }
109 }
110 else while (!queue.empty()) {
111 // Just delete all items in the queue; we already fired an event when
112 // initializing the backup failed.
113 auto call = std::move(queue.front());
114 queue.pop();
115
116 // We don't call the actual callback, so we have to make sure that
117 // the baton gets destroyed.
118 delete call->baton;
119 }
120}
121
122Backup::Backup(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Backup>(info) {
123 auto env = info.Env();
124 if (!info.IsConstructCall()) {
125 Napi::TypeError::New(env, "Use the new operator to create new Backup objects").ThrowAsJavaScriptException();
126 return;
127 }
128
129 auto length = info.Length();
130
131 if (length <= 0 || !Database::HasInstance(info[0])) {
132 Napi::TypeError::New(env, "Database object expected").ThrowAsJavaScriptException();
133 return;
134 }
135 else if (length <= 1 || !info[1].IsString()) {
136 Napi::TypeError::New(env, "Filename expected").ThrowAsJavaScriptException();
137 return;
138 }
139 else if (length <= 2 || !info[2].IsString()) {
140 Napi::TypeError::New(env, "Source database name expected").ThrowAsJavaScriptException();
141 return;
142 }
143 else if (length <= 3 || !info[3].IsString()) {
144 Napi::TypeError::New(env, "Destination database name expected").ThrowAsJavaScriptException();
145 return;
146 }
147 else if (length <= 4 || !info[4].IsBoolean()) {
148 Napi::TypeError::New(env, "Direction flag expected").ThrowAsJavaScriptException();
149 return;
150 }
151 else if (length > 5 && !info[5].IsUndefined() && !info[5].IsFunction()) {
152 Napi::TypeError::New(env, "Callback expected").ThrowAsJavaScriptException();
153 return;
154 }
155
156 this->db = Napi::ObjectWrap<Database>::Unwrap(info[0].As<Napi::Object>());
157 this->db->Ref();
158
159 auto filename = info[1].As<Napi::String>();
160 auto sourceName = info[2].As<Napi::String>();
161 auto destName = info[3].As<Napi::String>();
162 auto filenameIsDest = info[4].As<Napi::Boolean>();
163
164 info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("filename", filename));
165 info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("sourceName", sourceName));
166 info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("destName", destName));
167 info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("filenameIsDest", filenameIsDest));
168
169 auto* baton = new InitializeBaton(this->db, info[5].As<Napi::Function>(), this);
170 baton->filename = filename.Utf8Value();
171 baton->sourceName = sourceName.Utf8Value();
172 baton->destName = destName.Utf8Value();
173 baton->filenameIsDest = filenameIsDest.Value();
174
175 this->db->Schedule(Work_BeginInitialize, baton);
176}
177
178void Backup::Work_BeginInitialize(Database::Baton* baton) {
179 assert(baton->db->open);
180 baton->db->pending++;
181 auto env = baton->db->Env();
182 CREATE_WORK("sqlite3.Backup.Initialize", Work_Initialize, Work_AfterInitialize);
183}
184
185void Backup::Work_Initialize(napi_env e, void* data) {
186 BACKUP_INIT(InitializeBaton);
187
188 // In case stepping fails, we use a mutex to make sure we get the associated
189 // error message.
190 auto* mtx = sqlite3_db_mutex(baton->db->_handle);
191 sqlite3_mutex_enter(mtx);
192
193 backup->status = sqlite3_open(baton->filename.c_str(), &backup->_otherDb);
194
195 if (backup->status == SQLITE_OK) {
196 backup->_handle = sqlite3_backup_init(
197 baton->filenameIsDest ? backup->_otherDb : backup->db->_handle,
198 baton->destName.c_str(),
199 baton->filenameIsDest ? backup->db->_handle : backup->_otherDb,
200 baton->sourceName.c_str());
201 }
202 backup->_destDb = baton->filenameIsDest ? backup->_otherDb : backup->db->_handle;
203
204 if (backup->status != SQLITE_OK) {
205 backup->message = std::string(sqlite3_errmsg(backup->_destDb));
206 sqlite3_close(backup->_otherDb);
207 backup->_otherDb = NULL;
208 backup->_destDb = NULL;
209 }
210
211 sqlite3_mutex_leave(mtx);
212}
213
214void Backup::Work_AfterInitialize(napi_env e, napi_status status, void* data) {
215 std::unique_ptr<InitializeBaton> baton(static_cast<InitializeBaton*>(data));
216 auto* backup = baton->backup;
217
218 auto env = backup->Env();
219 Napi::HandleScope scope(env);
220
221 if (backup->status != SQLITE_OK) {
222 Error(baton.get());
223 backup->FinishAll();
224 }
225 else {
226 backup->inited = true;
227 Napi::Function cb = baton->callback.Value();
228 if (!cb.IsEmpty() && cb.IsFunction()) {
229 Napi::Value argv[] = { env.Null() };
230 TRY_CATCH_CALL(backup->Value(), cb, 1, argv);
231 }
232 }
233 BACKUP_END();
234}
235
236Napi::Value Backup::Step(const Napi::CallbackInfo& info) {
237 auto* backup = this;
238 auto env = backup->Env();
239
240 REQUIRE_ARGUMENT_INTEGER(0, pages);
241 OPTIONAL_ARGUMENT_FUNCTION(1, callback);
242
243 auto* baton = new StepBaton(backup, callback, pages);
244 backup->GetRetryErrors(baton->retryErrorsSet);
245 backup->Schedule(Work_BeginStep, baton);
246 return info.This();
247}
248
249void Backup::Work_BeginStep(Baton* baton) {
250 BACKUP_BEGIN(Step);
251}
252
253void Backup::Work_Step(napi_env e, void* data) {
254 BACKUP_INIT(StepBaton);
255 if (backup->_handle) {
256 backup->status = sqlite3_backup_step(backup->_handle, baton->pages);
257 backup->remaining = sqlite3_backup_remaining(backup->_handle);
258 backup->pageCount = sqlite3_backup_pagecount(backup->_handle);
259 }
260 if (backup->status != SQLITE_OK) {
261 // Text of message is a little awkward to get, since the error is not associated
262 // with a db connection.
263#if SQLITE_VERSION_NUMBER >= 3007015
264 // sqlite3_errstr is a relatively new method
265 backup->message = std::string(sqlite3_errstr(backup->status));
266#else
267 backup->message = "Sqlite error";
268#endif
269 if (baton->retryErrorsSet.size() > 0) {
270 if (baton->retryErrorsSet.find(backup->status) == baton->retryErrorsSet.end()) {
271 backup->FinishSqlite();
272 }
273 }
274 }
275}
276
277void Backup::Work_AfterStep(napi_env e, napi_status status, void* data) {
278 std::unique_ptr<StepBaton> baton(static_cast<StepBaton*>(data));
279 auto* backup = baton->backup;
280
281 auto env = backup->Env();
282 Napi::HandleScope scope(env);
283
284 if (backup->status == SQLITE_DONE) {
285 backup->completed = true;
286 } else if (!backup->_handle) {
287 backup->failed = true;
288 }
289
290 if (backup->status != SQLITE_OK && backup->status != SQLITE_DONE) {
291 Error(baton.get());
292 }
293 else {
294 // Fire callbacks.
295 Napi::Function cb = baton->callback.Value();
296 if (!cb.IsEmpty() && cb.IsFunction()) {
297 Napi::Value argv[] = { env.Null(), Napi::Boolean::New(env, backup->status == SQLITE_DONE) };
298 TRY_CATCH_CALL(backup->Value(), cb, 2, argv);
299 }
300 }
301
302 BACKUP_END();
303}
304
305Napi::Value Backup::Finish(const Napi::CallbackInfo& info) {
306 auto* backup = this;
307 auto env = backup->Env();
308
309 OPTIONAL_ARGUMENT_FUNCTION(0, callback);
310
311 auto* baton = new Baton(backup, callback);
312 backup->Schedule(Work_BeginFinish, baton);
313 return info.This();
314}
315
316void Backup::Work_BeginFinish(Baton* baton) {
317 BACKUP_BEGIN(Finish);
318}
319
320void Backup::Work_Finish(napi_env e, void* data) {
321 BACKUP_INIT(Baton);
322 backup->FinishSqlite();
323}
324
325void Backup::Work_AfterFinish(napi_env e, napi_status status, void* data) {
326 std::unique_ptr<Baton> baton(static_cast<Baton*>(data));
327 auto* backup = baton->backup;
328
329 auto env = backup->Env();
330 Napi::HandleScope scope(env);
331
332 backup->FinishAll();
333
334 // Fire callback in case there was one.
335 Napi::Function cb = baton->callback.Value();
336 if (!cb.IsEmpty() && cb.IsFunction()) {
337 TRY_CATCH_CALL(backup->Value(), cb, 0, NULL);
338 }
339
340 BACKUP_END();
341}
342
343void Backup::FinishAll() {
344 assert(!finished);
345 if (!completed && !failed) {
346 failed = true;
347 }
348 finished = true;
349 CleanQueue();
350 FinishSqlite();
351 db->Unref();
352}
353
354void Backup::FinishSqlite() {
355 if (_handle) {
356 sqlite3_backup_finish(_handle);
357 _handle = NULL;
358 }
359 if (_otherDb) {
360 sqlite3_close(_otherDb);
361 _otherDb = NULL;
362 }
363 _destDb = NULL;
364}
365
366Napi::Value Backup::IdleGetter(const Napi::CallbackInfo& info) {
367 auto* backup = this;
368 bool idle = backup->inited && !backup->locked && backup->queue.empty();
369 return Napi::Boolean::New(this->Env(), idle);
370}
371
372Napi::Value Backup::CompletedGetter(const Napi::CallbackInfo& info) {
373 auto* backup = this;
374 return Napi::Boolean::New(this->Env(), backup->completed);
375}
376
377Napi::Value Backup::FailedGetter(const Napi::CallbackInfo& info) {
378 auto* backup = this;
379 return Napi::Boolean::New(this->Env(), backup->failed);
380}
381
382Napi::Value Backup::RemainingGetter(const Napi::CallbackInfo& info) {
383 auto* backup = this;
384 return Napi::Number::New(this->Env(), backup->remaining);
385}
386
387Napi::Value Backup::PageCountGetter(const Napi::CallbackInfo& info) {
388 auto* backup = this;
389 return Napi::Number::New(this->Env(), backup->pageCount);
390}
391
392Napi::Value Backup::RetryErrorGetter(const Napi::CallbackInfo& info) {
393 auto* backup = this;
394 return backup->retryErrors.Value();
395}
396
397void Backup::RetryErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value) {
398 auto* backup = this;
399 auto env = backup->Env();
400 if (!value.IsArray()) {
401 Napi::Error::New(env, "retryErrors must be an array").ThrowAsJavaScriptException();
402 return;
403 }
404 Napi::Array array = value.As<Napi::Array>();
405 backup->retryErrors.Reset(array, 1);
406}
407
408void Backup::GetRetryErrors(std::set<int>& retryErrorsSet) {
409 retryErrorsSet.clear();
410 Napi::Array array = retryErrors.Value();
411 auto length = array.Length();
412 for (size_t i = 0; i < length; i++) {
413 Napi::Value code = (array).Get(static_cast<uint32_t>(i));
414 if (code.IsNumber()) {
415 retryErrorsSet.insert(code.As<Napi::Number>().Int32Value());
416 }
417 }
418}