@openzim/libzim 2.4.4 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/common.h ADDED
@@ -0,0 +1,150 @@
1
+ #pragma once
2
+
3
+ #include <napi.h>
4
+ #include <zim/zim.h>
5
+
6
+ #include <iostream>
7
+ #include <string>
8
+ #include <unordered_map>
9
+ #include <utility>
10
+ #include <vector>
11
+
12
+ using IntegrityCheckMap =
13
+ std::vector<std::pair<zim::IntegrityCheck, Napi::Reference<Napi::Symbol>>>;
14
+
15
+ using CompressionMap =
16
+ std::vector<std::pair<zim::Compression, Napi::Reference<Napi::Symbol>>>;
17
+
18
+ struct ModuleConstructors {
19
+ Napi::FunctionReference archive;
20
+ Napi::FunctionReference entry;
21
+ Napi::FunctionReference item;
22
+ Napi::FunctionReference blob;
23
+
24
+ Napi::FunctionReference searcher;
25
+ Napi::FunctionReference query;
26
+ Napi::FunctionReference search;
27
+ Napi::FunctionReference searchResultSet;
28
+ Napi::FunctionReference searchIterator;
29
+
30
+ Napi::FunctionReference suggestionSearcher;
31
+ Napi::FunctionReference suggestionSearch;
32
+ Napi::FunctionReference suggestionResultSet;
33
+ Napi::FunctionReference suggestionIterator;
34
+
35
+ Napi::FunctionReference stringProvider;
36
+ Napi::FunctionReference fileProvider;
37
+ Napi::FunctionReference creator;
38
+
39
+ Napi::FunctionReference stringItem;
40
+ Napi::FunctionReference fileItem;
41
+
42
+ Napi::FunctionReference compression;
43
+ CompressionMap compressionMap;
44
+
45
+ Napi::FunctionReference integrityCheck;
46
+ IntegrityCheckMap integrityCheckMap;
47
+ };
48
+
49
+ class Compression : public Napi::ObjectWrap<Compression> {
50
+ public:
51
+ explicit Compression(const Napi::CallbackInfo& info)
52
+ : Napi::ObjectWrap<Compression>(info) {}
53
+
54
+ static zim::Compression symbolToEnum(Napi::Env env,
55
+ const Napi::Value& value) {
56
+ if (!value.IsSymbol()) {
57
+ throw Napi::Error::New(env,
58
+ "Value must be a symbol for Compression value.");
59
+ }
60
+
61
+ auto& compressionMap =
62
+ env.GetInstanceData<ModuleConstructors>()->compressionMap;
63
+ Napi::HandleScope scope(env);
64
+ for (const auto& [bit, symbolRef] : compressionMap) {
65
+ if (!symbolRef.IsEmpty() && symbolRef.Value() == value) {
66
+ return bit;
67
+ }
68
+ }
69
+ throw Napi::Error::New(env, "Invalid Symbol for Compression value.");
70
+ }
71
+
72
+ static void Init(Napi::Env env, Napi::Object exports,
73
+ ModuleConstructors& constructors) {
74
+ Napi::HandleScope scope(env);
75
+
76
+ constexpr auto attrs =
77
+ static_cast<napi_property_attributes>(napi_default | napi_enumerable);
78
+ std::vector<PropertyDescriptor> props;
79
+ props.reserve(7);
80
+
81
+ const auto&& values = std::vector<std::pair<zim::Compression, const char*>>{
82
+ {zim::Compression::None, "None"},
83
+ {zim::Compression::Zstd, "Zstd"},
84
+ };
85
+ for (const auto& [value, name] : values) {
86
+ auto symbol = Napi::Symbol::New(env, name);
87
+ constructors.compressionMap.push_back({value, Napi::Persistent(symbol)});
88
+ props.push_back(StaticValue(name, symbol, attrs));
89
+ }
90
+
91
+ Napi::Function func = DefineClass(env, "Compression", props);
92
+ exports.Set("Compression", func);
93
+ constructors.compression = Napi::Persistent(func);
94
+ }
95
+ };
96
+
97
+ class IntegrityCheck : public Napi::ObjectWrap<IntegrityCheck> {
98
+ public:
99
+ explicit IntegrityCheck(const Napi::CallbackInfo& info)
100
+ : Napi::ObjectWrap<IntegrityCheck>(info) {}
101
+
102
+ static zim::IntegrityCheck symbolToEnum(Napi::Env env,
103
+ const Napi::Value& value) {
104
+ if (!value.IsSymbol()) {
105
+ throw Napi::Error::New(
106
+ env, "Value must be a symbol for IntegrityCheck value.");
107
+ }
108
+ auto& integrityCheckMap =
109
+ env.GetInstanceData<ModuleConstructors>()->integrityCheckMap;
110
+ Napi::HandleScope scope(env);
111
+ for (const auto& [bit, symbolRef] : integrityCheckMap) {
112
+ if (!symbolRef.IsEmpty() && symbolRef.Value() == value) {
113
+ return bit;
114
+ }
115
+ }
116
+ throw Napi::Error::New(env, "Invalid Symbol for IntegrityCheck value.");
117
+ }
118
+
119
+ static void Init(Napi::Env env, Napi::Object exports,
120
+ ModuleConstructors& constructors) {
121
+ Napi::HandleScope scope(env);
122
+
123
+ constexpr auto attrs =
124
+ static_cast<napi_property_attributes>(napi_default | napi_enumerable);
125
+ std::vector<PropertyDescriptor> props;
126
+ props.reserve(7);
127
+
128
+ const auto&& values =
129
+ std::vector<std::pair<zim::IntegrityCheck, const char*>>{
130
+ {zim::IntegrityCheck::CHECKSUM, "CHECKSUM"},
131
+ {zim::IntegrityCheck::DIRENT_PTRS, "DIRENT_PTRS"},
132
+ {zim::IntegrityCheck::DIRENT_ORDER, "DIRENT_ORDER"},
133
+ {zim::IntegrityCheck::TITLE_INDEX, "TITLE_INDEX"},
134
+ {zim::IntegrityCheck::CLUSTER_PTRS, "CLUSTER_PTRS"},
135
+ {zim::IntegrityCheck::DIRENT_MIMETYPES, "DIRENT_MIMETYPES"},
136
+ {zim::IntegrityCheck::COUNT, "COUNT"},
137
+ };
138
+ for (const auto& [value, name] : values) {
139
+ auto symbol = Napi::Symbol::New(env, name);
140
+ constructors.integrityCheckMap.push_back(
141
+ {value, Napi::Persistent(symbol)});
142
+ props.push_back(StaticValue(name, symbol, attrs));
143
+ }
144
+
145
+ Napi::Function func = DefineClass(env, "IntegrityCheck", props);
146
+ exports.Set("IntegrityCheck", func);
147
+ constructors.integrityCheck = Napi::Persistent(func);
148
+ }
149
+ };
150
+
@@ -0,0 +1,258 @@
1
+ #pragma once
2
+
3
+ #include <napi.h>
4
+ #include <zim/writer/contentProvider.h>
5
+
6
+ #include <exception>
7
+ #include <functional>
8
+ #include <future>
9
+ #include <memory>
10
+ #include <string_view>
11
+ #include <thread>
12
+ #include <utility>
13
+
14
+ #include "blob.h"
15
+ #include "common.h"
16
+
17
+ /**
18
+ * Wraps the js world ObjectWrap and Objects to a proper content provider for
19
+ * use with libzim
20
+ */
21
+ class ContentProviderWrapper : public zim::writer::ContentProvider {
22
+ public:
23
+ explicit ContentProviderWrapper(Napi::Env env, const Napi::Object &provider)
24
+ : MAIN_THREAD_ID{} {
25
+ MAIN_THREAD_ID = std::this_thread::get_id();
26
+
27
+ if (!provider.Get("feed").IsFunction()) {
28
+ throw std::runtime_error("ContentProvider.feed must be a function.");
29
+ }
30
+
31
+ auto feedFunc = provider.Get("feed").As<Napi::Function>();
32
+ feed_ = Napi::Persistent(feedFunc);
33
+ size_ = parseSize(provider.Get("size"));
34
+ provider_ = Napi::Persistent(provider);
35
+
36
+ tsfn_ = Napi::ThreadSafeFunction::New(env, feedFunc,
37
+ "getContentProvider.feed", 0, 1);
38
+ }
39
+
40
+ ~ContentProviderWrapper() { tsfn_.Release(); }
41
+
42
+ zim::size_type getSize() const override { return size_; }
43
+
44
+ /** Parse the size, supports BigInt */
45
+ static zim::size_type parseSize(const Napi::Value size) {
46
+ if (size.IsBigInt()) {
47
+ bool lossless;
48
+ auto &&val = size.As<Napi::BigInt>().Uint64Value(&lossless);
49
+ if (!lossless) {
50
+ throw std::runtime_error(
51
+ "size was not converted to a uint64_t losslessly");
52
+ }
53
+ return val;
54
+ }
55
+
56
+ auto val = size.ToNumber().Int64Value();
57
+ if (val < 0) {
58
+ throw std::runtime_error("Size must be greater than 0");
59
+ }
60
+ return static_cast<uint64_t>(val);
61
+ }
62
+
63
+ zim::Blob feed() override {
64
+ if (MAIN_THREAD_ID == std::this_thread::get_id()) {
65
+ // on main thread for some reason, do it here
66
+ auto blobObj = feed_.Call(provider_.Value(), {});
67
+ if (!blobObj.IsObject()) {
68
+ throw std::runtime_error("ContentProvider.feed must return a blob");
69
+ }
70
+ auto blob = Napi::ObjectWrap<Blob>::Unwrap(blobObj.ToObject());
71
+ return *(blob->blob());
72
+ }
73
+
74
+ // called from a thread
75
+ std::promise<zim::Blob> promise;
76
+ auto future = promise.get_future();
77
+
78
+ auto callback = [&promise, this](Napi::Env env, Napi::Function feedFunc) {
79
+ auto blobObj = feedFunc.Call(provider_.Value(), {});
80
+ if (!blobObj.IsObject()) {
81
+ throw std::runtime_error("ContentProvider.feed must return a blob");
82
+ }
83
+ auto blob = Napi::ObjectWrap<Blob>::Unwrap(blobObj.ToObject());
84
+ promise.set_value(*(blob->blob()));
85
+ };
86
+
87
+ auto status = tsfn_.BlockingCall(callback);
88
+ if (status != napi_ok) {
89
+ throw std::runtime_error("Error calling ThreadSafeFunction");
90
+ }
91
+
92
+ return future.get();
93
+ }
94
+
95
+ private:
96
+ // track the main thread
97
+ std::thread::id MAIN_THREAD_ID;
98
+ // js world reference, could be an ObjectWrap provider or custom js object
99
+ Napi::ObjectReference provider_;
100
+ Napi::FunctionReference feed_;
101
+ Napi::ThreadSafeFunction tsfn_;
102
+ zim::size_type size_;
103
+ };
104
+
105
+ class StringProvider : public Napi::ObjectWrap<StringProvider> {
106
+ public:
107
+ explicit StringProvider(const Napi::CallbackInfo &info)
108
+ : Napi::ObjectWrap<StringProvider>(info), provider_{nullptr} {
109
+ auto env = info.Env();
110
+ if (info.Length() < 1) {
111
+ throw Napi::Error::New(
112
+ env, "StringProvider requires an argument for a string.");
113
+ }
114
+
115
+ try {
116
+ if (info[0].IsExternal()) {
117
+ using ProviderPtr = std::unique_ptr<zim::writer::StringProvider>;
118
+ auto &&ptr = *info[0].As<Napi::External<ProviderPtr>>().Data();
119
+ provider_ = std::move(ptr);
120
+ } else {
121
+ auto str = info[0].ToString(); // value is coerced to a js string.
122
+ provider_ = std::make_unique<zim::writer::StringProvider>(str);
123
+ }
124
+ } catch (const std::exception &e) {
125
+ throw Napi::Error::New(env, e.what());
126
+ }
127
+ }
128
+
129
+ static Napi::Object New(
130
+ Napi::Env env,
131
+ std::unique_ptr<zim::writer::ContentProvider> stringProvider) {
132
+ auto &constructor =
133
+ env.GetInstanceData<ModuleConstructors>()->stringProvider;
134
+ auto external =
135
+ Napi::External<decltype(stringProvider)>::New(env, &stringProvider);
136
+ return constructor.New({external});
137
+ }
138
+
139
+ static Napi::Object New(Napi::Env env, const std::string_view value) {
140
+ auto &constructor =
141
+ env.GetInstanceData<ModuleConstructors>()->stringProvider;
142
+ auto str = Napi::String::New(env, value.data(), value.size());
143
+ return constructor.New({str});
144
+ }
145
+
146
+ Napi::Value getSize(const Napi::CallbackInfo &info) {
147
+ try {
148
+ return Napi::Value::From(info.Env(), provider_->getSize());
149
+ } catch (const std::exception &err) {
150
+ throw Napi::Error::New(info.Env(), err.what());
151
+ }
152
+ }
153
+
154
+ Napi::Value feed(const Napi::CallbackInfo &info) {
155
+ try {
156
+ // TODO(kelvinhammond): need a way to move this to avoid copying
157
+ auto blob = provider_->feed();
158
+ return Blob::New(info.Env(), blob);
159
+ } catch (const std::exception &err) {
160
+ throw Napi::Error::New(info.Env(), err.what());
161
+ }
162
+ }
163
+
164
+ static void Init(Napi::Env env, Napi::Object exports,
165
+ ModuleConstructors &constructors) {
166
+ Napi::HandleScope scope(env);
167
+ Napi::Function func =
168
+ DefineClass(env, "StringProvider",
169
+ {
170
+ InstanceAccessor<&StringProvider::getSize>("size"),
171
+ InstanceMethod<&StringProvider::getSize>("getSize"),
172
+ InstanceMethod<&StringProvider::feed>("feed"),
173
+ });
174
+
175
+ exports.Set("StringProvider", func);
176
+ constructors.stringProvider = Napi::Persistent(func);
177
+ }
178
+
179
+ private:
180
+ std::unique_ptr<zim::writer::StringProvider> provider_;
181
+ };
182
+
183
+ class FileProvider : public Napi::ObjectWrap<FileProvider> {
184
+ public:
185
+ explicit FileProvider(const Napi::CallbackInfo &info)
186
+ : Napi::ObjectWrap<FileProvider>(info), provider_{nullptr} {
187
+ auto env = info.Env();
188
+ if (info.Length() < 1) {
189
+ throw Napi::Error::New(
190
+ env, "FileProvider requires an argument for a filepath.");
191
+ }
192
+
193
+ try {
194
+ if (info[0].IsExternal()) {
195
+ using ProviderPtr = std::unique_ptr<zim::writer::FileProvider>;
196
+ auto &&ptr = *info[0].As<Napi::External<ProviderPtr>>().Data();
197
+ provider_ = std::move(ptr);
198
+ } else {
199
+ auto filepath = info[0].ToString(); // value is coerced to a js string.
200
+ provider_ = std::make_unique<zim::writer::FileProvider>(filepath);
201
+ }
202
+ } catch (const std::exception &e) {
203
+ throw Napi::Error::New(env, e.what());
204
+ }
205
+ }
206
+
207
+ static Napi::Object New(
208
+ Napi::Env env,
209
+ std::unique_ptr<zim::writer::ContentProvider> fileProvider) {
210
+ auto &constructor = env.GetInstanceData<ModuleConstructors>()->fileProvider;
211
+ auto external =
212
+ Napi::External<decltype(fileProvider)>::New(env, &fileProvider);
213
+ return constructor.New({external});
214
+ }
215
+
216
+ static Napi::Object New(Napi::Env env, const std::string_view filepath) {
217
+ auto &constructor = env.GetInstanceData<ModuleConstructors>()->fileProvider;
218
+ auto path = Napi::String::New(env, filepath.data(), filepath.size());
219
+ return constructor.New({path});
220
+ }
221
+
222
+ Napi::Value getSize(const Napi::CallbackInfo &info) {
223
+ try {
224
+ return Napi::Value::From(info.Env(), provider_->getSize());
225
+ } catch (const std::exception &err) {
226
+ throw Napi::Error::New(info.Env(), err.what());
227
+ }
228
+ }
229
+
230
+ Napi::Value feed(const Napi::CallbackInfo &info) {
231
+ try {
232
+ // TODO(kelvinhammond): need a way to move this to avoid copying
233
+ auto blob = provider_->feed();
234
+ return Blob::New(info.Env(), blob);
235
+ } catch (const std::exception &err) {
236
+ throw Napi::Error::New(info.Env(), err.what());
237
+ }
238
+ }
239
+
240
+ static void Init(Napi::Env env, Napi::Object exports,
241
+ ModuleConstructors &constructors) {
242
+ Napi::HandleScope scope(env);
243
+ Napi::Function func =
244
+ DefineClass(env, "FileProvider",
245
+ {
246
+ InstanceAccessor<&FileProvider::getSize>("size"),
247
+ InstanceMethod<&FileProvider::getSize>("getSize"),
248
+ InstanceMethod<&FileProvider::feed>("feed"),
249
+ });
250
+
251
+ exports.Set("FileProvider", func);
252
+ constructors.fileProvider = Napi::Persistent(func);
253
+ }
254
+
255
+ private:
256
+ std::unique_ptr<zim::writer::FileProvider> provider_;
257
+ };
258
+