@openzim/libzim 3.4.0 → 4.0.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/writerItem.h CHANGED
@@ -7,11 +7,13 @@
7
7
  #include <exception>
8
8
  #include <functional>
9
9
  #include <future>
10
+ #include <iostream>
10
11
  #include <memory>
11
12
  #include <optional>
12
13
  #include <string>
13
14
  #include <string_view>
14
15
  #include <thread>
16
+ #include <utility>
15
17
 
16
18
  #include "blob.h"
17
19
  #include "common.h"
@@ -89,18 +91,156 @@ class IndexDataWrapper : public zim::writer::IndexData {
89
91
  std::optional<GeoPosition> position_;
90
92
  };
91
93
 
94
+ /**
95
+ * Wraps a the getIndexData JS function to a ThreadSafeFunction
96
+ */
97
+ class GetIndexDataTSFN {
98
+ public:
99
+ using IndexDataWrapperPtr = std::shared_ptr<IndexDataWrapper>;
100
+
101
+ GetIndexDataTSFN() = delete;
102
+
103
+ GetIndexDataTSFN(Napi::Env &env, Napi::Function &indexDataFunc) {
104
+ tsfn_ =
105
+ TSFN::New(env,
106
+ indexDataFunc, // JavaScript function called asynchronously
107
+ "GetIndexDataTSFN", // name
108
+ 0, // max queue size (0 = unlimited).
109
+ 1, // initial thread count
110
+ nullptr); // context
111
+ }
112
+
113
+ ~GetIndexDataTSFN() { tsfn_.Release(); }
114
+
115
+ IndexDataWrapperPtr getIndexData() {
116
+ try {
117
+ DataType promise;
118
+ auto future = promise.get_future();
119
+ tsfn_.NonBlockingCall(&promise);
120
+ return future.get();
121
+ } catch (const std::exception &e) {
122
+ std::cerr << "GetIndexDataTSFN getIndexData() exception: " << e.what()
123
+ << std::endl;
124
+ throw std::runtime_error(
125
+ std::string("Error in GetIndexDataTSFN getIndexData(): ") + e.what());
126
+ }
127
+ }
128
+
129
+ private:
130
+ using DataType = std::promise<IndexDataWrapperPtr>;
131
+ using Context = void;
132
+
133
+ static void CallJs(Napi::Env env, Napi::Function callback, Context *context,
134
+ DataType *data) {
135
+ // Is the JavaScript environment still available to call into, eg. the TSFN
136
+ // is not aborted
137
+ if (env != nullptr) {
138
+ try {
139
+ // call getIndexData(): object
140
+ auto result = callback.Call({});
141
+ if (result.IsObject()) {
142
+ auto indexData =
143
+ std::make_shared<IndexDataWrapper>(result.As<Napi::Object>());
144
+ data->set_value(indexData);
145
+ } else {
146
+ data->set_exception(std::make_exception_ptr(
147
+ std::runtime_error("Expected an object from getIndexData")));
148
+ }
149
+ } catch (const std::exception &e) {
150
+ data->set_exception(std::make_exception_ptr(e));
151
+ }
152
+ } else {
153
+ data->set_exception(std::make_exception_ptr(
154
+ std::runtime_error("Environment is shut down")));
155
+ }
156
+ }
157
+
158
+ using TSFN = Napi::TypedThreadSafeFunction<Context, DataType, CallJs>;
159
+
160
+ private:
161
+ TSFN tsfn_;
162
+ };
163
+
164
+ /**
165
+ * Wraps a the getContentProvider JS function to a ThreadSafeFunction
166
+ */
167
+ class GetContentProviderTSFN {
168
+ public:
169
+ using ContentProviderWrapperPtr = std::unique_ptr<ContentProviderWrapper>;
170
+
171
+ GetContentProviderTSFN() = delete;
172
+
173
+ GetContentProviderTSFN(Napi::Env &env, Napi::Function &providerFunc) {
174
+ tsfn_ =
175
+ TSFN::New(env,
176
+ providerFunc, // JavaScript function called asynchronously
177
+ "GetContentProviderTSFN", // name
178
+ 0, // max queue size (0 = unlimited).
179
+ 1, // initial thread count
180
+ nullptr); // context
181
+ }
182
+
183
+ ~GetContentProviderTSFN() { tsfn_.Release(); }
184
+
185
+ ContentProviderWrapperPtr getContentProvider() {
186
+ try {
187
+ DataType promise;
188
+ auto future = promise.get_future();
189
+ tsfn_.NonBlockingCall(&promise);
190
+ return future.get();
191
+ } catch (const std::exception &e) {
192
+ std::cerr << "GetContentProviderTSFN getContentProvider() exception: "
193
+ << e.what() << std::endl;
194
+ throw std::runtime_error(
195
+ std::string(
196
+ "Error in GetContentProviderTSFN getContentProvider(): ") +
197
+ e.what());
198
+ }
199
+ }
200
+
201
+ private:
202
+ using DataType = std::promise<ContentProviderWrapperPtr>;
203
+ using Context = void;
204
+
205
+ static void CallJs(Napi::Env env, Napi::Function callback, Context *context,
206
+ DataType *data) {
207
+ // Is the JavaScript environment still available to call into, eg. the
208
+ // TSFN is not aborted
209
+ if (env != nullptr) {
210
+ try {
211
+ // call getContentProvider(): object
212
+ auto result = callback.Call({});
213
+ if (result.IsObject()) {
214
+ auto provider = std::make_unique<ContentProviderWrapper>(
215
+ env, result.As<Napi::Object>());
216
+ data->set_value(std::move(provider));
217
+ } else {
218
+ data->set_exception(std::make_exception_ptr(std::runtime_error(
219
+ "Expected an object from getContentProvider")));
220
+ }
221
+ } catch (const std::exception &e) {
222
+ data->set_exception(std::make_exception_ptr(e));
223
+ }
224
+ } else {
225
+ data->set_exception(std::make_exception_ptr(
226
+ std::runtime_error("Environment is shut down")));
227
+ }
228
+ }
229
+
230
+ using TSFN = Napi::TypedThreadSafeFunction<Context, DataType, CallJs>;
231
+
232
+ private:
233
+ TSFN tsfn_;
234
+ };
235
+
92
236
  /**
93
237
  * Wraps a JS World Item to a zim::writer::Item
94
238
  *
95
- * NOTE: should be initialized on the main thread
239
+ * NOTE: MUST BE initialized on the main thread
96
240
  */
97
241
  class ItemWrapper : public zim::writer::Item {
98
242
  public:
99
- ItemWrapper(Napi::Env env, Napi::Object item)
100
- : MAIN_THREAD_ID{}, item_{}, hasIndexDataImpl_{false} {
101
- MAIN_THREAD_ID = std::this_thread::get_id();
102
- item_ = Napi::Persistent(item);
103
-
243
+ ItemWrapper(Napi::Env env, Napi::Object item) {
104
244
  path_ = item.Get("path").ToString();
105
245
  title_ = item.Get("title").ToString();
106
246
  mimeType_ = item.Get("mimeType").ToString();
@@ -118,9 +258,8 @@ class ItemWrapper : public zim::writer::Item {
118
258
  }
119
259
 
120
260
  auto indexDataFunc = indexDataFuncValue.As<Napi::Function>();
121
- indexDataFunc_ = Napi::Persistent(indexDataFunc);
122
- indexDataTSNF_ = Napi::ThreadSafeFunction::New(
123
- env, indexDataFunc, "ItemWrapper.indexData", 0, 1);
261
+ getIndexDataTSFN_ =
262
+ std::make_unique<GetIndexDataTSFN>(env, indexDataFunc);
124
263
  }
125
264
 
126
265
  auto providerFuncValue = item.Get("getContentProvider");
@@ -128,16 +267,8 @@ class ItemWrapper : public zim::writer::Item {
128
267
  throw std::runtime_error("getContentProvider must be a function");
129
268
  }
130
269
  auto providerFunc = providerFuncValue.As<Napi::Function>();
131
- contentProviderFunc_ = Napi::Persistent(providerFunc);
132
- contentProviderTSNF_ = Napi::ThreadSafeFunction::New(
133
- env, providerFunc, "ItemWrapper.contentProvider", 5, 1);
134
- }
135
-
136
- ~ItemWrapper() {
137
- if (hasIndexDataImpl_) {
138
- indexDataTSNF_.Release();
139
- }
140
- contentProviderTSNF_.Release();
270
+ getContentProviderTSFN_ =
271
+ std::make_unique<GetContentProviderTSFN>(env, providerFunc);
141
272
  }
142
273
 
143
274
  std::string getPath() const override { return path_; }
@@ -154,31 +285,11 @@ class ItemWrapper : public zim::writer::Item {
154
285
  return zim::writer::Item::getIndexData();
155
286
  }
156
287
 
157
- if (MAIN_THREAD_ID == std::this_thread::get_id()) {
158
- auto data = indexDataFunc_.Call(item_.Value(), {});
159
- return data.IsObject()
160
- ? std::make_shared<IndexDataWrapper>(data.ToObject())
161
- : nullptr;
162
- }
163
-
164
- // called from a thread
165
- using IndexDataWrapperPtr = std::shared_ptr<IndexDataWrapper>;
166
- std::promise<IndexDataWrapperPtr> promise;
167
- auto future = promise.get_future();
168
-
169
- auto callback = [&promise, this](Napi::Env env, Napi::Function idxFunc) {
170
- auto data = idxFunc.Call(item_.Value(), {});
171
- promise.set_value(
172
- data.IsObject() ? std::make_shared<IndexDataWrapper>(data.ToObject())
173
- : nullptr);
174
- };
175
-
176
- auto status = indexDataTSNF_.BlockingCall(callback);
177
- if (status != napi_ok) {
178
- throw std::runtime_error("Error calling indexData ThreadSafeFunction");
288
+ if (getIndexDataTSFN_ == nullptr) {
289
+ throw std::runtime_error("Error: getIndexDataTSFN_ is null");
179
290
  }
180
291
 
181
- return future.get();
292
+ return getIndexDataTSFN_->getIndexData();
182
293
  }
183
294
 
184
295
  /**
@@ -187,65 +298,23 @@ class ItemWrapper : public zim::writer::Item {
187
298
  */
188
299
  std::unique_ptr<zim::writer::ContentProvider> getContentProvider()
189
300
  const override {
190
- if (MAIN_THREAD_ID == std::this_thread::get_id()) {
191
- auto env = contentProviderFunc_.Env();
192
- auto provider = contentProviderFunc_.Call(item_.Value(), {});
193
- if (provider.IsObject()) {
194
- return std::make_unique<ContentProviderWrapper>(env,
195
- provider.ToObject());
196
- } else if (provider.IsNull() || provider.IsUndefined()) {
197
- return nullptr;
198
- }
199
-
200
- throw std::runtime_error(
201
- "getContentProvider must return an object or null");
202
- }
203
-
204
- using ContentProviderWrapperPtr = std::unique_ptr<ContentProviderWrapper>;
205
- std::promise<ContentProviderWrapperPtr> promise;
206
- auto future = promise.get_future();
207
-
208
- auto callback = [&promise, this](Napi::Env env,
209
- Napi::Function providerFunc) {
210
- auto provider = providerFunc.Call(item_.Value(), {});
211
- if (provider.IsObject()) {
212
- auto ptr =
213
- std::make_unique<ContentProviderWrapper>(env, provider.ToObject());
214
- promise.set_value(std::move(ptr));
215
- } else if (provider.IsNull() || provider.IsUndefined()) {
216
- promise.set_value(nullptr);
217
- } else {
218
- throw std::runtime_error(
219
- "getContentProvider must return an object or null");
220
- }
221
- };
222
-
223
- auto status = contentProviderTSNF_.BlockingCall(callback);
224
- if (status != napi_ok) {
225
- throw std::runtime_error(
226
- "Error calling contentProvider ThreadSafeFunction");
227
- }
228
-
229
- return future.get();
301
+ return getContentProviderTSFN_->getContentProvider();
230
302
  }
231
303
 
232
304
  private:
233
- std::thread::id MAIN_THREAD_ID;
234
- // js world reference, could be an ObjectWrap provider or custom js object
235
- Napi::ObjectReference item_;
236
-
237
305
  std::string path_;
238
306
  std::string title_;
239
307
  std::string mimeType_;
240
308
  zim::writer::Hints hints_;
241
309
  bool hasIndexDataImpl_;
242
310
 
243
- Napi::FunctionReference indexDataFunc_;
244
- Napi::ThreadSafeFunction indexDataTSNF_;
245
- Napi::FunctionReference contentProviderFunc_;
246
- Napi::ThreadSafeFunction contentProviderTSNF_;
311
+ std::unique_ptr<GetContentProviderTSFN> getContentProviderTSFN_;
312
+ std::unique_ptr<GetIndexDataTSFN> getIndexDataTSFN_;
247
313
  };
248
314
 
315
+ /**
316
+ * Wraps a zim::writer::StringItem
317
+ */
249
318
  class StringItem : public Napi::ObjectWrap<StringItem> {
250
319
  public:
251
320
  explicit StringItem(const Napi::CallbackInfo &info)
@@ -340,9 +409,17 @@ class StringItem : public Napi::ObjectWrap<StringItem> {
340
409
 
341
410
  // TODO(kelvinhammond): implement getIndexData for StringItem and FileItem
342
411
 
412
+ static bool InstanceOf(Napi::Env env, Napi::Object obj) {
413
+ Napi::FunctionReference &constructor = GetConstructor(env);
414
+ return obj.InstanceOf(constructor.Value());
415
+ }
416
+
417
+ static Napi::FunctionReference &GetConstructor(Napi::Env env) {
418
+ return env.GetInstanceData<ModuleConstructors>()->stringItem;
419
+ }
420
+
343
421
  static void Init(Napi::Env env, Napi::Object exports,
344
422
  ModuleConstructors &constructors) {
345
- Napi::HandleScope scope(env);
346
423
  Napi::Function func =
347
424
  DefineClass(env, "StringItem",
348
425
  {
@@ -362,6 +439,9 @@ class StringItem : public Napi::ObjectWrap<StringItem> {
362
439
  std::shared_ptr<zim::writer::StringItem> item_;
363
440
  };
364
441
 
442
+ /**
443
+ * Wraps a zim::writer::FileItem
444
+ */
365
445
  class FileItem : public Napi::ObjectWrap<FileItem> {
366
446
  public:
367
447
  explicit FileItem(const Napi::CallbackInfo &info)
@@ -439,9 +519,17 @@ class FileItem : public Napi::ObjectWrap<FileItem> {
439
519
  }
440
520
  }
441
521
 
522
+ static bool InstanceOf(Napi::Env env, Napi::Object obj) {
523
+ Napi::FunctionReference &constructor = GetConstructor(env);
524
+ return obj.InstanceOf(constructor.Value());
525
+ }
526
+
527
+ static Napi::FunctionReference &GetConstructor(Napi::Env env) {
528
+ return env.GetInstanceData<ModuleConstructors>()->fileItem;
529
+ }
530
+
442
531
  static void Init(Napi::Env env, Napi::Object exports,
443
532
  ModuleConstructors &constructors) {
444
- Napi::HandleScope scope(env);
445
533
  Napi::Function func = DefineClass(
446
534
  env, "FileItem",
447
535
  {
package/src/entryrange.h DELETED
@@ -1,106 +0,0 @@
1
- #pragma once
2
-
3
- #include <napi.h>
4
- #include <zim/entry.h>
5
- #include <exception>
6
- #include <memory>
7
-
8
- #include "entry.h"
9
-
10
- class EntryRange : public Napi::ObjectWrap<EntryRange> {
11
- public:
12
- static constexpr const char *ENTRY_RANGE_CONSTRUCTOR_NAME = "EntryRange";
13
-
14
- explicit EntryRange(const Napi::CallbackInfo &info)
15
- : Napi::ObjectWrap<EntryRange>(info) {
16
- auto env = info.Env();
17
-
18
- if (!info[0].IsExternal()) {
19
- throw Napi::Error::New(
20
- env, "EntryRange must be constructed internally by another class.");
21
- }
22
-
23
- if (info[0].IsExternal()) {
24
- try {
25
- entry_ = std::make_shared<zim::Entry>(
26
- *info[0].As<Napi::External<zim::Entry>>().Data());
27
- } catch (const std::exception &err) {
28
- throw Napi::Error::New(env, err.what());
29
- }
30
- }
31
- }
32
-
33
- template <typename RangeT>
34
- static Napi::Object New(Napi::Env env, RangeT range) {
35
- Napi::Function iterator = Napi::Function::New(
36
- env, [range](const Napi::CallbackInfo &info) mutable -> Napi::Value {
37
- Napi::Env env = info.Env();
38
- Napi::Object iter = Napi::Object::New(env);
39
-
40
- auto it = range.begin();
41
- iter["next"] = Napi::Function::New(
42
- env,
43
- [range,
44
- it](const Napi::CallbackInfo &info) mutable -> Napi::Value {
45
- Napi::Env env = info.Env();
46
- Napi::Object res = Napi::Object::New(env);
47
- if (it != range.end()) {
48
- res["done"] = false;
49
- res["value"] = Entry::New(env, zim::Entry(*it));
50
- it++;
51
- } else {
52
- res["done"] = true;
53
- }
54
- return res;
55
- });
56
- return iter;
57
- });
58
-
59
- auto offset = Napi::Function::New(
60
- env, [range](const Napi::CallbackInfo &info) -> Napi::Value {
61
- if (info.Length() < 2) {
62
- throw Napi::Error::New(
63
- info.Env(), "start and maxResults are required for offset.");
64
- }
65
- if (!(info[0].IsNumber() && info[1].IsNumber())) {
66
- throw Napi::Error::New(
67
- info.Env(), "start and maxResults must be of type Number.");
68
- }
69
- auto start = info[0].ToNumber();
70
- auto maxResults = info[1].ToNumber();
71
- return NewEntryRange(info.Env(), range.offset(start, maxResults));
72
- });
73
-
74
- auto size = Napi::Value::From(env, range.size());
75
-
76
- auto &constructor = env.GetInstanceData<ConstructorsMap>()->at(
77
- ENTRY_RANGE_CONSTRUCTOR_NAME);
78
- return constructor.New({iterator, size, offset});
79
- }
80
-
81
- Napi::Value getSize(const Napi::CallbackInfo &info) {
82
- try {
83
- return Napi::Value::From(info.Env(), entry_->getIndex());
84
- } catch (const std::exception &err) {
85
- throw Napi::Error::New(info.Env(), err.what());
86
- }
87
- }
88
-
89
- static void Init(Napi::Env env, Napi::Object exports,
90
- ConstructorsMap &constructors) {
91
- Napi::HandleScope scope(env);
92
- Napi::Function func =
93
- DefineClass(env, "EntryRange",
94
- {
95
- InstanceAccessor<&EntryRange::getSize>("size"),
96
- InstanceMethod<&EntryRange::getItem>("getItem"),
97
- });
98
-
99
- exports.Set("EntryRange", func);
100
- constructors.insert_or_assign(ENTRY_RANGE_CONSTRUCTOR_NAME,
101
- Napi::Persistent(func));
102
- }
103
-
104
- private:
105
- std::shared_ptr<zim::Entry> entry_;
106
- };