aboutsummaryrefslogtreecommitdiff
path: root/updater/sql/win/src/backup.h
diff options
context:
space:
mode:
Diffstat (limited to 'updater/sql/win/src/backup.h')
-rwxr-xr-xupdater/sql/win/src/backup.h209
1 files changed, 209 insertions, 0 deletions
diff --git a/updater/sql/win/src/backup.h b/updater/sql/win/src/backup.h
new file mode 100755
index 0000000..b894aca
--- /dev/null
+++ b/updater/sql/win/src/backup.h
@@ -0,0 +1,209 @@
+#ifndef NODE_SQLITE3_SRC_BACKUP_H
+#define NODE_SQLITE3_SRC_BACKUP_H
+
+#include "database.h"
+
+#include <string>
+#include <queue>
+#include <set>
+
+#include <sqlite3.h>
+#include <napi.h>
+
+using namespace Napi;
+
+namespace node_sqlite3 {
+
+/**
+ *
+ * A class for managing an sqlite3_backup object. For consistency
+ * with other node-sqlite3 classes, it maintains an internal queue
+ * of calls.
+ *
+ * Intended usage from node:
+ *
+ * var db = new sqlite3.Database('live.db');
+ * var backup = db.backup('backup.db');
+ * ...
+ * // in event loop, move backup forward when we have time.
+ * if (backup.idle) { backup.step(NPAGES); }
+ * if (backup.completed) { ... success ... }
+ * if (backup.failed) { ... sadness ... }
+ * // do other work in event loop - fine to modify live.db
+ * ...
+ *
+ * Here is how sqlite's backup api is exposed:
+ *
+ * - `sqlite3_backup_init`: This is implemented as
+ * `db.backup(filename, [callback])` or
+ * `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`.
+ * - `sqlite3_backup_step`: `backup.step(pages, [callback])`.
+ * - `sqlite3_backup_finish`: `backup.finish([callback])`.
+ * - `sqlite3_backup_remaining`: `backup.remaining`.
+ * - `sqlite3_backup_pagecount`: `backup.pageCount`.
+ *
+ * There are the following read-only properties:
+ *
+ * - `backup.completed` is set to `true` when the backup
+ * succeeeds.
+ * - `backup.failed` is set to `true` when the backup
+ * has a fatal error.
+ * - `backup.idle` is set to `true` when no operation
+ * is currently in progress or queued for the backup.
+ * - `backup.remaining` is an integer with the remaining
+ * number of pages after the last call to `backup.step`
+ * (-1 if `step` not yet called).
+ * - `backup.pageCount` is an integer with the total number
+ * of pages measured during the last call to `backup.step`
+ * (-1 if `step` not yet called).
+ *
+ * There is the following writable property:
+ *
+ * - `backup.retryErrors`: an array of sqlite3 error codes
+ * that are treated as non-fatal - meaning, if they occur,
+ * backup.failed is not set, and the backup may continue.
+ * By default, this is `[sqlite3.BUSY, sqlite3.LOCKED]`.
+ *
+ * The `db.backup(filename, [callback])` shorthand is sufficient
+ * for making a backup of a database opened by node-sqlite3. If
+ * using attached or temporary databases, or moving data in the
+ * opposite direction, the more complete (but daunting)
+ * `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`
+ * signature is provided.
+ *
+ * A backup will finish automatically when it succeeds or a fatal
+ * error occurs, meaning it is not necessary to call `db.finish()`.
+ * By default, SQLITE_LOCKED and SQLITE_BUSY errors are not
+ * treated as failures, and the backup will continue if they
+ * occur. The set of errors that are tolerated can be controlled
+ * by setting `backup.retryErrors`. To disable automatic
+ * finishing and stick strictly to sqlite's raw api, set
+ * `backup.retryErrors` to `[]`. In that case, it is necessary
+ * to call `backup.finish()`.
+ *
+ * In the same way as node-sqlite3 databases and statements,
+ * backup methods can be called safely without callbacks, due
+ * to an internal call queue. So for example this naive code
+ * will correctly back up a db, if there are no errors:
+ *
+ * var backup = db.backup('backup.db');
+ * backup.step(-1);
+ * backup.finish();
+ *
+ */
+class Backup : public Napi::ObjectWrap<Backup> {
+public:
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
+
+ struct Baton {
+ napi_async_work request = NULL;
+ Backup* backup;
+ Napi::FunctionReference callback;
+
+ Baton(Backup* backup_, Napi::Function cb_) : backup(backup_) {
+ backup->Ref();
+ callback.Reset(cb_, 1);
+ }
+ virtual ~Baton() {
+ if (request) napi_delete_async_work(backup->Env(), request);
+ backup->Unref();
+ callback.Reset();
+ }
+ };
+
+ struct InitializeBaton : Database::Baton {
+ Backup* backup;
+ std::string filename;
+ std::string sourceName;
+ std::string destName;
+ bool filenameIsDest;
+ InitializeBaton(Database* db_, Napi::Function cb_, Backup* backup_) :
+ Baton(db_, cb_), backup(backup_), filenameIsDest(true) {
+ backup->Ref();
+ }
+ virtual ~InitializeBaton() override {
+ backup->Unref();
+ if (!db->IsOpen() && db->IsLocked()) {
+ // The database handle was closed before the backup could be opened.
+ backup->FinishAll();
+ }
+ }
+ };
+
+ struct StepBaton : Baton {
+ int pages;
+ std::set<int> retryErrorsSet;
+ StepBaton(Backup* backup_, Napi::Function cb_, int pages_) :
+ Baton(backup_, cb_), pages(pages_) {}
+ virtual ~StepBaton() override = default;
+ };
+
+ typedef void (*Work_Callback)(Baton* baton);
+
+ struct Call {
+ Call(Work_Callback cb_, Baton* baton_) : callback(cb_), baton(baton_) {};
+ Work_Callback callback;
+ Baton* baton;
+ };
+
+ Backup(const Napi::CallbackInfo& info);
+
+ ~Backup() {
+ if (!finished) {
+ FinishAll();
+ }
+ retryErrors.Reset();
+ }
+
+ WORK_DEFINITION(Step)
+ WORK_DEFINITION(Finish)
+
+ Napi::Value IdleGetter(const Napi::CallbackInfo& info);
+ Napi::Value CompletedGetter(const Napi::CallbackInfo& info);
+ Napi::Value FailedGetter(const Napi::CallbackInfo& info);
+ Napi::Value PageCountGetter(const Napi::CallbackInfo& info);
+ Napi::Value RemainingGetter(const Napi::CallbackInfo& info);
+ Napi::Value FatalErrorGetter(const Napi::CallbackInfo& info);
+ Napi::Value RetryErrorGetter(const Napi::CallbackInfo& info);
+
+ void FatalErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value);
+ void RetryErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value);
+
+protected:
+ static void Work_BeginInitialize(Database::Baton* baton);
+ static void Work_Initialize(napi_env env, void* data);
+ static void Work_AfterInitialize(napi_env env, napi_status status, void* data);
+
+ void Schedule(Work_Callback callback, Baton* baton);
+ void Process();
+ void CleanQueue();
+ template <class T> static void Error(T* baton);
+
+ void FinishAll();
+ void FinishSqlite();
+ void GetRetryErrors(std::set<int>& retryErrorsSet);
+
+ Database* db;
+
+ sqlite3_backup* _handle = NULL;
+ sqlite3* _otherDb = NULL;
+ sqlite3* _destDb = NULL;
+
+ bool inited = false;
+ bool locked = true;
+ bool completed = false;
+ bool failed = false;
+ int remaining = -1;
+ int pageCount = -1;
+ bool finished = false;
+
+ int status;
+ std::string message;
+ std::queue<std::unique_ptr<Call>> queue;
+
+ Napi::Reference<Array> retryErrors;
+};
+
+}
+
+#endif