@openzim/libzim 3.5.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/blob.h CHANGED
@@ -12,13 +12,11 @@
12
12
  class Blob : public Napi::ObjectWrap<Blob> {
13
13
  public:
14
14
  explicit Blob(const Napi::CallbackInfo &info)
15
- : Napi::ObjectWrap<Blob>(info), blob_{std::make_shared<zim::Blob>()} {
16
- Napi::Env env = info.Env();
17
- Napi::HandleScope scope(env);
18
-
19
- if (info[0].IsExternal()) { // handle internal zim::Blob
20
- blob_ = std::make_shared<zim::Blob>(
21
- *info[0].As<Napi::External<zim::Blob>>().Data());
15
+ : Napi::ObjectWrap<Blob>(info), blob_{} {
16
+ if (info[0].IsExternal()) {
17
+ // handle internal zim::Blob
18
+ // Copy blob (shared_ptr) from external
19
+ blob_ = zim::Blob(*info[0].As<Napi::External<zim::Blob>>().Data());
22
20
  } else if (info.Length() > 0) { // use refcontent_ and copy content
23
21
  // TODO(kelvinhammond): avoid copying content somehow in certain scenarios
24
22
  // if possible Maybe use a reference object??? What is the lifecycle of
@@ -38,28 +36,31 @@ class Blob : public Napi::ObjectWrap<Blob> {
38
36
  data = std::shared_ptr<char>(new char[size],
39
37
  std::default_delete<char[]>());
40
38
  memcpy(data.get(), buf.Data(), size);
41
- } else { // all others toString()
42
- auto str = info[0].ToString().Utf8Value(); // coerce to string
39
+ } else if (info[0].IsString()) { // all others toString()
40
+ auto str = info[0].As<Napi::String>().Utf8Value(); // coerce to string
43
41
  size = str.size();
44
42
  data = std::shared_ptr<char>(new char[size],
45
43
  std::default_delete<char[]>());
46
44
  memcpy(data.get(), str.c_str(), size);
45
+ } else {
46
+ throw Napi::Error::New(
47
+ info.Env(),
48
+ "Blob constructor expects an ArrayBuffer, Buffer, or String");
47
49
  }
48
50
 
49
- blob_ = std::make_shared<zim::Blob>(data, size); // blob takes ownership
51
+ blob_ = zim::Blob(data, size); // blob takes ownership
50
52
  }
51
53
  }
52
54
 
53
55
  static Napi::Object New(Napi::Env env, zim::Blob &blob) {
54
56
  auto external = Napi::External<zim::Blob>::New(env, &blob);
55
- auto &constructor = env.GetInstanceData<ModuleConstructors>()->blob;
56
- return constructor.New({external});
57
+ return GetConstructor(env).New({external});
57
58
  }
58
59
 
59
60
  Napi::Value getData(const Napi::CallbackInfo &info) {
60
61
  try {
61
62
  // TODO(kelvinhammond): find a way to have a readonly buffer in NodeJS
62
- return Napi::Buffer<char>::Copy(info.Env(), blob_->data(), blob_->size());
63
+ return Napi::Buffer<char>::Copy(info.Env(), blob_.data(), blob_.size());
63
64
  } catch (const std::exception &err) {
64
65
  throw Napi::Error::New(info.Env(), err.what());
65
66
  }
@@ -67,7 +68,7 @@ class Blob : public Napi::ObjectWrap<Blob> {
67
68
 
68
69
  Napi::Value toString(const Napi::CallbackInfo &info) {
69
70
  try {
70
- return Napi::Value::From(info.Env(), (std::string)*blob_);
71
+ return Napi::Value::From(info.Env(), (std::string)blob_);
71
72
  } catch (const std::exception &err) {
72
73
  throw Napi::Error::New(info.Env(), err.what());
73
74
  }
@@ -75,15 +76,27 @@ class Blob : public Napi::ObjectWrap<Blob> {
75
76
 
76
77
  Napi::Value getSize(const Napi::CallbackInfo &info) {
77
78
  try {
78
- return Napi::Value::From(info.Env(), blob_->size());
79
+ return Napi::Value::From(info.Env(), blob_.size());
79
80
  } catch (const std::exception &err) {
80
81
  throw Napi::Error::New(info.Env(), err.what());
81
82
  }
82
83
  }
83
84
 
85
+ static bool InstanceOf(Napi::Env env, Napi::Value value) {
86
+ if (!value.IsObject()) {
87
+ return false;
88
+ }
89
+ Napi::Object obj = value.As<Napi::Object>();
90
+ Napi::FunctionReference &constructor = GetConstructor(env);
91
+ return obj.InstanceOf(constructor.Value());
92
+ }
93
+
94
+ static Napi::FunctionReference &GetConstructor(Napi::Env env) {
95
+ return env.GetInstanceData<ModuleConstructors>()->blob;
96
+ }
97
+
84
98
  static void Init(Napi::Env env, Napi::Object exports,
85
99
  ModuleConstructors &constructors) {
86
- Napi::HandleScope scope(env);
87
100
  Napi::Function func =
88
101
  DefineClass(env, "Blob",
89
102
  {
@@ -97,8 +110,8 @@ class Blob : public Napi::ObjectWrap<Blob> {
97
110
  }
98
111
 
99
112
  // internal module methods
100
- std::shared_ptr<zim::Blob> blob() const { return blob_; }
113
+ const zim::Blob &blob() const { return blob_; }
101
114
 
102
115
  private:
103
- std::shared_ptr<zim::Blob> blob_;
116
+ zim::Blob blob_;
104
117
  };
package/src/common.h CHANGED
@@ -17,6 +17,8 @@ using CompressionMap =
17
17
 
18
18
  struct ModuleConstructors {
19
19
  Napi::FunctionReference archive;
20
+ Napi::FunctionReference openConfig;
21
+ Napi::FunctionReference illustrationInfo;
20
22
  Napi::FunctionReference entry;
21
23
  Napi::FunctionReference item;
22
24
  Napi::FunctionReference blob;
@@ -60,7 +62,6 @@ class Compression : public Napi::ObjectWrap<Compression> {
60
62
 
61
63
  auto& compressionMap =
62
64
  env.GetInstanceData<ModuleConstructors>()->compressionMap;
63
- Napi::HandleScope scope(env);
64
65
  for (const auto& [bit, symbolRef] : compressionMap) {
65
66
  if (!symbolRef.IsEmpty() && symbolRef.Value() == value) {
66
67
  return bit;
@@ -71,8 +72,6 @@ class Compression : public Napi::ObjectWrap<Compression> {
71
72
 
72
73
  static void Init(Napi::Env env, Napi::Object exports,
73
74
  ModuleConstructors& constructors) {
74
- Napi::HandleScope scope(env);
75
-
76
75
  constexpr auto attrs =
77
76
  static_cast<napi_property_attributes>(napi_default | napi_enumerable);
78
77
  std::vector<PropertyDescriptor> props;
@@ -107,7 +106,6 @@ class IntegrityCheck : public Napi::ObjectWrap<IntegrityCheck> {
107
106
  }
108
107
  auto& integrityCheckMap =
109
108
  env.GetInstanceData<ModuleConstructors>()->integrityCheckMap;
110
- Napi::HandleScope scope(env);
111
109
  for (const auto& [bit, symbolRef] : integrityCheckMap) {
112
110
  if (!symbolRef.IsEmpty() && symbolRef.Value() == value) {
113
111
  return bit;
@@ -118,8 +116,6 @@ class IntegrityCheck : public Napi::ObjectWrap<IntegrityCheck> {
118
116
 
119
117
  static void Init(Napi::Env env, Napi::Object exports,
120
118
  ModuleConstructors& constructors) {
121
- Napi::HandleScope scope(env);
122
-
123
119
  constexpr auto attrs =
124
120
  static_cast<napi_property_attributes>(napi_default | napi_enumerable);
125
121
  std::vector<PropertyDescriptor> props;
@@ -6,7 +6,9 @@
6
6
  #include <exception>
7
7
  #include <functional>
8
8
  #include <future>
9
+ #include <iostream>
9
10
  #include <memory>
11
+ #include <string>
10
12
  #include <string_view>
11
13
  #include <thread>
12
14
  #include <utility>
@@ -14,31 +16,92 @@
14
16
  #include "blob.h"
15
17
  #include "common.h"
16
18
 
19
+ /**
20
+ * Thread Safe Function wrapper for calling the ContentProvider.feed function
21
+ * asynchronously from libzim
22
+ */
23
+ class FeedTSFN {
24
+ public:
25
+ using BlobPtr = zim::Blob;
26
+
27
+ FeedTSFN() = delete;
28
+
29
+ FeedTSFN(Napi::Env &env, Napi::Function &feedFunc) {
30
+ tsfn_ = TSFN::New(env,
31
+ feedFunc, // JavaScript function called asynchronously
32
+ "FeedTSFN", // name
33
+ 0, // max queue size (0 = unlimited).
34
+ 1, // initial thread count
35
+ nullptr); // context
36
+ }
37
+
38
+ ~FeedTSFN() { tsfn_.Release(); }
39
+
40
+ BlobPtr feed() {
41
+ try {
42
+ DataType promise;
43
+ auto future = promise.get_future();
44
+ tsfn_.NonBlockingCall(&promise);
45
+ return future.get();
46
+ } catch (const std::exception &e) {
47
+ std::cerr << "FeedTSFN feed() exception: " << e.what() << std::endl;
48
+ throw std::runtime_error(std::string("Error in FeedTSFN feed(): ") +
49
+ e.what());
50
+ }
51
+ }
52
+
53
+ private:
54
+ using DataType = std::promise<BlobPtr>;
55
+ using Context = void;
56
+
57
+ static void CallJs(Napi::Env env, Napi::Function callback, Context *context,
58
+ DataType *data) {
59
+ // Is the JavaScript environment still available to call into, eg. the TSFN
60
+ // is not aborted
61
+ if (env != nullptr) {
62
+ try {
63
+ // call feed(): object
64
+ auto result = callback.Call({});
65
+ if (Blob::InstanceOf(env, result)) {
66
+ auto blob = Blob::Unwrap(result.As<Napi::Object>())->blob();
67
+ // Note: Cannot move, blob could be used in nodejs world still
68
+ data->set_value(blob);
69
+ } else {
70
+ data->set_exception(std::make_exception_ptr(std::runtime_error(
71
+ "Expected an object of type Blob from feed()")));
72
+ }
73
+ } catch (const std::exception &e) {
74
+ data->set_exception(std::make_exception_ptr(e));
75
+ }
76
+ } else {
77
+ data->set_exception(std::make_exception_ptr(
78
+ std::runtime_error("Environment is shut down")));
79
+ }
80
+ }
81
+
82
+ using TSFN = Napi::TypedThreadSafeFunction<Context, DataType, CallJs>;
83
+
84
+ private:
85
+ TSFN tsfn_;
86
+ };
87
+
17
88
  /**
18
89
  * Wraps the js world ObjectWrap and Objects to a proper content provider for
19
90
  * use with libzim
20
91
  */
21
92
  class ContentProviderWrapper : public zim::writer::ContentProvider {
22
93
  public:
23
- explicit ContentProviderWrapper(Napi::Env env, const Napi::Object &provider)
24
- : MAIN_THREAD_ID{} {
25
- MAIN_THREAD_ID = std::this_thread::get_id();
94
+ explicit ContentProviderWrapper(Napi::Env env, const Napi::Object &provider) {
95
+ size_ = parseSize(provider.Get("size"));
26
96
 
27
97
  if (!provider.Get("feed").IsFunction()) {
28
98
  throw std::runtime_error("ContentProvider.feed must be a function.");
29
99
  }
30
100
 
31
101
  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);
102
+ feedTSFN_ = std::make_unique<FeedTSFN>(env, feedFunc);
38
103
  }
39
104
 
40
- ~ContentProviderWrapper() { tsfn_.Release(); }
41
-
42
105
  zim::size_type getSize() const override { return size_; }
43
106
 
44
107
  /** Parse the size, supports BigInt */
@@ -60,46 +123,13 @@ class ContentProviderWrapper : public zim::writer::ContentProvider {
60
123
  return static_cast<uint64_t>(val);
61
124
  }
62
125
 
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
- }
126
+ zim::Blob feed() override { return feedTSFN_->feed(); }
94
127
 
95
128
  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
129
  zim::size_type size_;
130
+ // Unique pointer so that it isn't copied and the destructor will only be
131
+ // called once
132
+ std::unique_ptr<FeedTSFN> feedTSFN_;
103
133
  };
104
134
 
105
135
  class StringProvider : public Napi::ObjectWrap<StringProvider> {
@@ -144,6 +174,11 @@ class StringProvider : public Napi::ObjectWrap<StringProvider> {
144
174
  }
145
175
 
146
176
  Napi::Value getSize(const Napi::CallbackInfo &info) {
177
+ if (!provider_) {
178
+ throw Napi::Error::New(
179
+ info.Env(), "StringProvider has been moved and is no longer valid.");
180
+ }
181
+
147
182
  try {
148
183
  return Napi::Value::From(info.Env(), provider_->getSize());
149
184
  } catch (const std::exception &err) {
@@ -152,8 +187,12 @@ class StringProvider : public Napi::ObjectWrap<StringProvider> {
152
187
  }
153
188
 
154
189
  Napi::Value feed(const Napi::CallbackInfo &info) {
190
+ if (!provider_) {
191
+ throw Napi::Error::New(
192
+ info.Env(), "StringProvider has been moved and is no longer valid.");
193
+ }
194
+
155
195
  try {
156
- // TODO(kelvinhammond): need a way to move this to avoid copying
157
196
  auto blob = provider_->feed();
158
197
  return Blob::New(info.Env(), blob);
159
198
  } catch (const std::exception &err) {
@@ -161,9 +200,21 @@ class StringProvider : public Napi::ObjectWrap<StringProvider> {
161
200
  }
162
201
  }
163
202
 
203
+ static bool InstanceOf(Napi::Env env, Napi::Value value) {
204
+ if (!value.IsObject()) {
205
+ return false;
206
+ }
207
+ Napi::Object obj = value.As<Napi::Object>();
208
+ Napi::FunctionReference &constructor = GetConstructor(env);
209
+ return obj.InstanceOf(constructor.Value());
210
+ }
211
+
212
+ static Napi::FunctionReference &GetConstructor(Napi::Env env) {
213
+ return env.GetInstanceData<ModuleConstructors>()->stringProvider;
214
+ }
215
+
164
216
  static void Init(Napi::Env env, Napi::Object exports,
165
217
  ModuleConstructors &constructors) {
166
- Napi::HandleScope scope(env);
167
218
  Napi::Function func =
168
219
  DefineClass(env, "StringProvider",
169
220
  {
@@ -176,6 +227,11 @@ class StringProvider : public Napi::ObjectWrap<StringProvider> {
176
227
  constructors.stringProvider = Napi::Persistent(func);
177
228
  }
178
229
 
230
+ // Internal use only
231
+ std::unique_ptr<zim::writer::StringProvider> &&unwrapProvider() {
232
+ return std::move(provider_);
233
+ }
234
+
179
235
  private:
180
236
  std::unique_ptr<zim::writer::StringProvider> provider_;
181
237
  };
@@ -220,6 +276,11 @@ class FileProvider : public Napi::ObjectWrap<FileProvider> {
220
276
  }
221
277
 
222
278
  Napi::Value getSize(const Napi::CallbackInfo &info) {
279
+ if (!provider_) {
280
+ throw Napi::Error::New(
281
+ info.Env(), "FileProvider has been moved and is no longer valid.");
282
+ }
283
+
223
284
  try {
224
285
  return Napi::Value::From(info.Env(), provider_->getSize());
225
286
  } catch (const std::exception &err) {
@@ -228,8 +289,12 @@ class FileProvider : public Napi::ObjectWrap<FileProvider> {
228
289
  }
229
290
 
230
291
  Napi::Value feed(const Napi::CallbackInfo &info) {
292
+ if (!provider_) {
293
+ throw Napi::Error::New(
294
+ info.Env(), "FileProvider has been moved and is no longer valid.");
295
+ }
296
+
231
297
  try {
232
- // TODO(kelvinhammond): need a way to move this to avoid copying
233
298
  auto blob = provider_->feed();
234
299
  return Blob::New(info.Env(), blob);
235
300
  } catch (const std::exception &err) {
@@ -237,9 +302,21 @@ class FileProvider : public Napi::ObjectWrap<FileProvider> {
237
302
  }
238
303
  }
239
304
 
305
+ static bool InstanceOf(Napi::Env env, Napi::Value value) {
306
+ if (!value.IsObject()) {
307
+ return false;
308
+ }
309
+ Napi::Object obj = value.As<Napi::Object>();
310
+ Napi::FunctionReference &constructor = GetConstructor(env);
311
+ return obj.InstanceOf(constructor.Value());
312
+ }
313
+
314
+ static Napi::FunctionReference &GetConstructor(Napi::Env env) {
315
+ return env.GetInstanceData<ModuleConstructors>()->fileProvider;
316
+ }
317
+
240
318
  static void Init(Napi::Env env, Napi::Object exports,
241
319
  ModuleConstructors &constructors) {
242
- Napi::HandleScope scope(env);
243
320
  Napi::Function func =
244
321
  DefineClass(env, "FileProvider",
245
322
  {
@@ -252,6 +329,11 @@ class FileProvider : public Napi::ObjectWrap<FileProvider> {
252
329
  constructors.fileProvider = Napi::Persistent(func);
253
330
  }
254
331
 
332
+ // Internal use only
333
+ std::unique_ptr<zim::writer::FileProvider> &&unwrapProvider() {
334
+ return std::move(provider_);
335
+ }
336
+
255
337
  private:
256
338
  std::unique_ptr<zim::writer::FileProvider> provider_;
257
339
  };
package/src/creator.h CHANGED
@@ -1,15 +1,19 @@
1
1
  #pragma once
2
2
 
3
3
  #include <napi.h>
4
+ #include <zim/illustration.h>
4
5
  #include <zim/writer/creator.h>
5
6
 
6
7
  #include <exception>
7
8
  #include <functional>
9
+ #include <iostream>
10
+ #include <map>
8
11
  #include <memory>
9
12
  #include <string>
10
13
  #include <utility>
11
14
 
12
15
  #include "common.h"
16
+ #include "illustration.h"
13
17
  #include "writerItem.h"
14
18
 
15
19
  // Handles creator_->finishZimCreation() operations in the background off the
@@ -24,7 +28,14 @@ class CreatorAsyncWorker : public Napi::AsyncWorker {
24
28
 
25
29
  ~CreatorAsyncWorker() {}
26
30
 
27
- void Execute() override { creator_->finishZimCreation(); }
31
+ void Execute() override {
32
+ try {
33
+ creator_->finishZimCreation();
34
+ } catch (const std::exception &e) {
35
+ std::cerr << "Error: finishZimCreation failed: " << e.what() << std::endl;
36
+ SetError(e.what());
37
+ }
38
+ }
28
39
 
29
40
  void OnOK() override {
30
41
  auto env = Env();
@@ -54,7 +65,15 @@ class AddItemAsyncWorker : public Napi::AsyncWorker {
54
65
 
55
66
  Napi::Promise Promise() const { return promise_.Promise(); };
56
67
 
57
- void Execute() override { creator_->addItem(item_); }
68
+ void Execute() override {
69
+ try {
70
+ creator_->addItem(item_);
71
+ } catch (const std::exception &e) {
72
+ std::cerr << "Error: AddItemAsyncWorker failed: " << e.what()
73
+ << std::endl;
74
+ SetError(e.what());
75
+ }
76
+ }
58
77
 
59
78
  void OnOK() override {
60
79
  auto env = Env();
@@ -163,16 +182,11 @@ class Creator : public Napi::ObjectWrap<Creator> {
163
182
  throw Napi::Error::New(env, "addItem requires an item object");
164
183
  }
165
184
 
166
- const auto &stringItem =
167
- env.GetInstanceData<ModuleConstructors>()->stringItem.Value();
168
- const auto &fileItem =
169
- env.GetInstanceData<ModuleConstructors>()->fileItem.Value();
170
-
171
185
  std::shared_ptr<zim::writer::Item> item{};
172
- auto obj = info[0].ToObject();
173
- if (obj.InstanceOf(stringItem)) {
186
+ auto obj = info[0].As<Napi::Object>();
187
+ if (StringItem::InstanceOf(env, obj)) {
174
188
  item = Napi::ObjectWrap<StringItem>::Unwrap(obj)->getItem();
175
- } else if (obj.InstanceOf(fileItem)) {
189
+ } else if (FileItem::InstanceOf(env, obj)) {
176
190
  item = Napi::ObjectWrap<FileItem>::Unwrap(obj)->getItem();
177
191
  } else {
178
192
  item = std::make_shared<ItemWrapper>(env, info[0].ToObject());
@@ -218,9 +232,28 @@ class Creator : public Napi::ObjectWrap<Creator> {
218
232
 
219
233
  auto name = info[0].ToString().Utf8Value();
220
234
  auto content = info[1];
221
- if (content.IsObject()) { // content provider
222
- std::unique_ptr<zim::writer::ContentProvider> provider =
223
- std::make_unique<ContentProviderWrapper>(env, content.ToObject());
235
+ if (content.IsObject()) {
236
+ // addMetadata(name: string, content: ContentProvider, [mimetype])
237
+ auto obj = content.As<Napi::Object>();
238
+ std::unique_ptr<zim::writer::ContentProvider> provider{nullptr};
239
+
240
+ // Determine the type of ContentProvider
241
+ if (StringProvider::InstanceOf(env, content)) {
242
+ auto wrapped = Napi::ObjectWrap<StringProvider>::Unwrap(obj);
243
+ provider = wrapped->unwrapProvider();
244
+ } else if (FileProvider::InstanceOf(env, content)) {
245
+ auto wrapped = Napi::ObjectWrap<FileProvider>::Unwrap(obj);
246
+ provider = wrapped->unwrapProvider();
247
+ } else {
248
+ // Fallback to generic ContentProviderWrapper
249
+ provider = std::make_unique<ContentProviderWrapper>(env, obj);
250
+ }
251
+
252
+ if (provider == nullptr) {
253
+ throw Napi::Error::New(
254
+ env, "addMetadata failed to create ContentProvider from object");
255
+ }
256
+
224
257
  if (info.Length() > 2) { // preserves default argument
225
258
  auto mimetype = info[2].ToString().Utf8Value();
226
259
  creator_->addMetadata(name, std::move(provider), mimetype);
@@ -228,7 +261,7 @@ class Creator : public Napi::ObjectWrap<Creator> {
228
261
  const std::string mimetype = "text/plain;charset=utf-8";
229
262
  creator_->addMetadata(name, std::move(provider), mimetype);
230
263
  }
231
- } else { // string version
264
+ } else { // addMetadata(name: string, content: string, [mimetype])
232
265
  auto str = content.ToString().Utf8Value();
233
266
  if (info.Length() > 2) {
234
267
  auto mimetype = info[2].ToString().Utf8Value();
@@ -238,6 +271,7 @@ class Creator : public Napi::ObjectWrap<Creator> {
238
271
  }
239
272
  }
240
273
  } catch (const std::exception &err) {
274
+ std::cerr << "Error: addMetadata failed: " << err.what() << std::endl;
241
275
  throw Napi::Error::New(info.Env(), err.what());
242
276
  }
243
277
  }
@@ -245,15 +279,40 @@ class Creator : public Napi::ObjectWrap<Creator> {
245
279
  void addIllustration(const Napi::CallbackInfo &info) {
246
280
  try {
247
281
  auto env = info.Env();
248
- auto size = info[0].ToNumber().Uint32Value();
249
- auto content = info[1];
250
- if (content.IsObject()) {
251
- std::unique_ptr<zim::writer::ContentProvider> provider =
252
- std::make_unique<ContentProviderWrapper>(env, content.ToObject());
253
- creator_->addIllustration(size, std::move(provider));
282
+
283
+ // Inline template function to handle both size and IllustrationInfo
284
+ const auto addIllusWithContent = [&](auto &opt1) {
285
+ auto content = info[1];
286
+ if (content.IsObject()) {
287
+ std::unique_ptr<zim::writer::ContentProvider> provider =
288
+ std::make_unique<ContentProviderWrapper>(env, content.ToObject());
289
+ creator_->addIllustration(opt1, std::move(provider));
290
+ } else {
291
+ auto str = content.ToString().Utf8Value();
292
+ creator_->addIllustration(opt1, str);
293
+ }
294
+ };
295
+
296
+ auto arg0 = info[0];
297
+ if (arg0.IsNumber()) {
298
+ auto size = arg0.ToNumber().Uint32Value();
299
+ addIllusWithContent(size);
300
+ } else if (arg0.IsObject()) {
301
+ // Parse as IllustrationInfo
302
+ auto obj = arg0.ToObject();
303
+
304
+ // getIllustrationItem(illusInfo: IllustrationInfo)
305
+ // getIllustrationItem(illusInfo: object)
306
+ auto illusInfo =
307
+ IllustrationInfo::InstanceOf(env, obj)
308
+ ? IllustrationInfo::Unwrap(obj)->getInternalIllustrationInfo()
309
+ : IllustrationInfo::infoFrom(obj);
310
+ addIllusWithContent(illusInfo);
254
311
  } else {
255
- auto str = content.ToString().Utf8Value();
256
- creator_->addIllustration(size, str);
312
+ throw Napi::Error::New(
313
+ env,
314
+ "addIllustration first argument must be size[number] or "
315
+ "IllustrationInfo[object]");
257
316
  }
258
317
  } catch (const std::exception &err) {
259
318
  throw Napi::Error::New(info.Env(), err.what());
@@ -315,7 +374,6 @@ class Creator : public Napi::ObjectWrap<Creator> {
315
374
 
316
375
  static void Init(Napi::Env env, Napi::Object exports,
317
376
  ModuleConstructors &constructors) {
318
- Napi::HandleScope scope(env);
319
377
  Napi::Function func = DefineClass(
320
378
  env, "Creator",
321
379
  {
package/src/entry.h CHANGED
@@ -93,7 +93,6 @@ class Entry : public Napi::ObjectWrap<Entry> {
93
93
 
94
94
  static void Init(Napi::Env env, Napi::Object exports,
95
95
  ModuleConstructors &constructors) {
96
- Napi::HandleScope scope(env);
97
96
  Napi::Function func = DefineClass(
98
97
  env, "Entry",
99
98
  {