🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@openzim/libzim

Package Overview
Dependencies
Maintainers
2
Versions
68
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@openzim/libzim - npm Package Compare versions

Comparing version
3.5.0
to
4.0.0
+167
src/illustration.h
#pragma once
#include <napi.h>
#include <zim/illustration.h>
#include <exception>
#include <map>
#include <string>
class IllustrationInfo : public Napi::ObjectWrap<IllustrationInfo> {
public:
explicit IllustrationInfo(const Napi::CallbackInfo &info)
: Napi::ObjectWrap<IllustrationInfo>(info), ii_{} {
Napi::Env env = info.Env();
if (info.Length() == 0) {
// Default constructor
ii_ = zim::IllustrationInfo{};
return;
}
// IllustrationInfo(ii: External<zim::IllustrationInfo>)
// Construct from external, used internally
if (info[0].IsExternal()) {
auto ext = info[0].As<Napi::External<zim::IllustrationInfo>>();
ii_ = *(ext.Data());
return;
}
if (!info[0].IsObject()) {
throw Napi::Error::New(env,
"IllustrationInfo constructor expects an "
"IllustrationInfo like object");
}
auto value = info[0];
// IllustrationInfo(ii: IllustrationInfo)
// Copy constructor
if (InstanceOf(env, value)) {
auto unwrapped = Unwrap(value.As<Napi::Object>());
ii_ = zim::IllustrationInfo(unwrapped->ii_);
return;
}
// IllustrationInfo(ii: object)
// Construct from object
ii_ = infoFrom(value.As<Napi::Object>());
}
static Napi::Object New(Napi::Env env, const zim::IllustrationInfo &ii) {
Napi::FunctionReference &constructor = GetConstructor(env);
auto data = Napi::External<zim::IllustrationInfo>::New(
env, new zim::IllustrationInfo(ii),
[](Napi::BasicEnv /*env*/, zim::IllustrationInfo *ptr) { delete ptr; });
return constructor.New({data});
}
/**
* Construct zim::IllustrationInfo from a JS object
*/
static zim::IllustrationInfo infoFrom(Napi::Object obj) {
zim::IllustrationInfo ii{
.width =
obj.Has("width") ? obj.Get("width").ToNumber().Uint32Value() : 0,
.height =
obj.Has("height") ? obj.Get("height").ToNumber().Uint32Value() : 0,
.scale =
obj.Has("scale") ? obj.Get("scale").ToNumber().FloatValue() : 0.0f,
.extraAttributes = std::map<std::string, std::string>{},
};
if (obj.Has("extraAttributes") && obj.Get("extraAttributes").IsObject()) {
auto extraAttributes = obj.Get("extraAttributes").ToObject();
auto keys = extraAttributes.GetPropertyNames();
for (const auto &e : keys) {
auto key =
static_cast<Napi::Value>(e.second).As<Napi::String>().Utf8Value();
auto value = extraAttributes.Get(key).ToString().Utf8Value();
ii.extraAttributes[key] = value;
}
}
return ii;
}
Napi::Value getWidth(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
return Napi::Value::From(env, ii_.width);
}
Napi::Value getHeight(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
return Napi::Value::From(env, ii_.height);
}
Napi::Value getScale(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
return Napi::Value::From(env, ii_.scale);
}
Napi::Value getExtraAttributes(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::Object obj = Napi::Object::New(env);
for (const auto &pair : ii_.extraAttributes) {
obj.Set(pair.first, pair.second);
}
obj.Freeze();
return obj;
}
Napi::Value asMetadataItemName(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
return Napi::Value::From(env, ii_.asMetadataItemName());
}
static Napi::Value fromMetadataItemName(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsString()) {
throw Napi::TypeError::New(
env, "First argument must be a string for metadata item name.");
}
auto name = info[0].As<Napi::String>().Utf8Value();
auto ii = zim::IllustrationInfo::fromMetadataItemName(name);
return New(env, ii);
}
static bool InstanceOf(Napi::Env env, Napi::Value value) {
if (!value.IsObject()) {
return false;
}
Napi::Object obj = value.As<Napi::Object>();
Napi::FunctionReference &constructor = GetConstructor(env);
return obj.InstanceOf(constructor.Value());
}
static Napi::FunctionReference &GetConstructor(Napi::Env env) {
auto constructors = env.GetInstanceData<ModuleConstructors>();
return constructors->illustrationInfo;
}
zim::IllustrationInfo &getInternalIllustrationInfo() { return ii_; }
static void Init(Napi::Env env, Napi::Object exports,
ModuleConstructors &constructors) {
Napi::Function func = DefineClass(
env, "IllustrationInfo",
{
InstanceAccessor<&IllustrationInfo::getWidth>("width"),
InstanceAccessor<&IllustrationInfo::getHeight>("height"),
InstanceAccessor<&IllustrationInfo::getScale>("scale"),
InstanceAccessor<&IllustrationInfo::getExtraAttributes>(
"extraAttributes"),
InstanceMethod<&IllustrationInfo::asMetadataItemName>(
"asMetadataItemName"),
StaticMethod<&IllustrationInfo::fromMetadataItemName>(
"fromMetadataItemName"),
});
exports.Set("IllustrationInfo", func);
constructors.illustrationInfo = Napi::Persistent(func);
}
private:
zim::IllustrationInfo ii_;
};
#pragma once
#include <napi.h>
#include <zim/zim.h>
class OpenConfig : public Napi::ObjectWrap<OpenConfig> {
public:
explicit OpenConfig(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<OpenConfig>(info), config_{zim::OpenConfig()} {
if (info.Length() > 0) {
throw Napi::Error::New(
info.Env(), "OpenConfig constructor does not take any arguments.");
}
}
Napi::Value preloadXapianDb(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsBoolean()) {
throw Napi::TypeError::New(
env, "First argument must be a boolean for preloadXapianDb.");
}
bool value = info[0].As<Napi::Boolean>().Value();
config_.preloadXapianDb(value);
return info.This();
}
Napi::Value preloadDirentRanges(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsNumber()) {
throw Napi::TypeError::New(
env, "First argument must be a number for preloadDirentRanges.");
}
int value = info[0].As<Napi::Number>().Int32Value();
config_.preloadDirentRanges(value);
return info.This();
}
Napi::Value getPreloadXapianDb(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::Value::From(env, config_.m_preloadXapianDb);
}
Napi::Value getPreloadDirentRanges(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::Value::From(env, config_.m_preloadDirentRanges);
}
static Napi::FunctionReference& GetConstructor(Napi::Env env) {
return env.GetInstanceData<ModuleConstructors>()->openConfig;
}
static bool InstanceOf(Napi::Env env, Napi::Value value) {
if (!value.IsObject()) {
return false;
}
Napi::Object obj = value.As<Napi::Object>();
Napi::FunctionReference& constructor = GetConstructor(env);
return obj.InstanceOf(constructor.Value());
}
const zim::OpenConfig& getInternalConfig() const { return config_; }
static void Init(Napi::Env env, Napi::Object exports,
ModuleConstructors& constructors) {
Napi::Function func = DefineClass(
env, "OpenConfig",
{
InstanceMethod<&OpenConfig::preloadXapianDb>("preloadXapianDb"),
InstanceMethod<&OpenConfig::preloadDirentRanges>(
"preloadDirentRanges"),
InstanceAccessor<&OpenConfig::getPreloadXapianDb>(
"m_preloadXapianDb"),
InstanceAccessor<&OpenConfig::getPreloadDirentRanges>(
"m_preloadDirentRanges"),
});
exports.Set("OpenConfig", func);
constructors.openConfig = Napi::Persistent(func);
}
private:
zim::OpenConfig config_;
};
+1
-1

@@ -1,1 +0,1 @@

LIBZIM_VERSION=9.3.0
LIBZIM_VERSION=9.4.0

@@ -22,3 +22,3 @@ import dotenv from "dotenv";

if (rawArch === "arm64") {
libDir = "aarch64-linux-gnu";
libDir = "aarch64-rpi3-linux-gnu";
} else if (rawArch === "arm") {

@@ -25,0 +25,0 @@ libDir = "arm-linux-gnueabihf";

@@ -0,1 +1,5 @@

export declare function getClusterCacheMaxSize(): number;
export declare function getClusterCacheCurrentSize(): number;
export declare function setClusterCacheMaxSize(nbClusters: number): void;
export class IntegrityCheck {

@@ -107,3 +111,6 @@ static CHECKSUM: symbol;

): void;
addIllustration(size: number, content: string | ContentProvider): void;
addIllustration(
sizeOrInfo: number | IIllustrationInfo,
content: string | ContentProvider,
): void;
addRedirection(

@@ -150,4 +157,31 @@ path: string,

export interface IIllustrationInfo {
width?: number;
height?: number;
scale?: number;
extraAttributes?: Record<string, string>;
}
export class IllustrationInfo implements IIllustrationInfo {
constructor(info?: IIllustrationInfo | IllustrationInfo);
get width(): number;
get height(): number;
get scale(): number;
get extraAttributes(): Record<string, string>;
asMetadataItemName(): string;
static fromMetadataItemName(name: string): IllustrationInfo;
}
export class OpenConfig {
constructor();
preloadXapianDb(preload: boolean): this;
preloadDirentRanges(nbRanges: number): this;
get m_preloadXapianDb(): boolean;
get m_preloadDirentRanges(): number;
}
export class Archive {
constructor(filepath: string);
constructor(filepath: string, config?: OpenConfig);
get filename(): string;

@@ -163,4 +197,10 @@ get filesize(): number | bigint;

get metadataKeys(): string[];
getIllustrationItem(size: number): Item;
getIllustrationItem(sizeOrInfo?: number | IIllustrationInfo): Item;
get illustrationSizes(): Set<number>;
getIllustrationInfos(
width?: number,
height?: number,
minScale?: number,
): IllustrationInfo[];
get illustrationInfos(): IllustrationInfo[];
getEntryByPath(path_or_idx: string | number): Entry;

@@ -188,10 +228,5 @@ getEntryByTitle(title_or_idx: string | number): Entry;

get hasNewNamespaceScheme(): boolean;
getClusterCacheMaxSize(): number;
getClusterCacheCurrentSize(): number;
setClusterCacheMaxSize(nbClusters: number): void;
getDirentCacheMaxSize(): number;
getDirentCacheCurrentSize(): number;
setDirentCacheMaxSize(nbDirents: number): void;
getDirentLookupCacheMaxSize(): number;
setDirentLookupCacheMaxSize(nbRanges: number): void;

@@ -198,0 +233,0 @@ static validate(zimPath: string, checksToRun: symbol[]): boolean; // list of IntegrityCheck

@@ -5,5 +5,7 @@ import bindings from "bindings";

Archive,
OpenConfig,
Entry,
IntegrityCheck,
Compression,
IllustrationInfo,
Blob,

@@ -18,2 +20,5 @@ Searcher,

FileItem,
getClusterCacheMaxSize,
getClusterCacheCurrentSize,
setClusterCacheMaxSize,
} = bindings("zim_binding");

@@ -8,3 +8,2 @@ import dotenv from "dotenv";

import fs from "fs";
import urlParser from "url";

@@ -50,3 +49,3 @@ mkdirp.sync("./download");

console.info(`Downloading Libzim from: `, url);
const filename = urlParser.parse(url).pathname.split("/").slice(-1)[0];
const filename = new URL(url).pathname.split("/").slice(-1)[0];
const dlFile = `./download/${filename}`;

@@ -53,0 +52,0 @@

@@ -5,3 +5,3 @@ {

"types": "dist/index.d.js",
"version": "3.5.0",
"version": "4.0.0",
"description": "Libzim bindings for NodeJS",

@@ -18,4 +18,4 @@ "type": "module",

"bundle": "node ./bundle-libzim.js",
"test": "jest --testPathIgnorePatterns=test/dist.test.ts",
"test-mem-leak": "node -r ts-node/register test/makeLargeZim.ts",
"test": "jest --testPathIgnorePatterns=test/dist.test.ts --testPathIgnorePatterns=dist/",
"test-mem-leak": "npx tsx test/makeLargeZim.ts",
"test:dist": "npm run build && jest",

@@ -49,27 +49,27 @@ "lint": "npx eslint .",

"dependencies": {
"@types/bindings": "^1.5.1",
"@types/jest": "^29.5.12",
"@types/node": "^18.0.6",
"axios": "^1.6.0",
"@types/bindings": "^1.5.5",
"@types/jest": "^30.0.0",
"@types/node": "^24.9.2",
"axios": "^1.13.1",
"bindings": "^1.5.0",
"dotenv": "^16.0.1",
"dotenv": "^17.2.3",
"exec-then": "^1.3.1",
"mkdirp": "^3.0.1",
"node-addon-api": "^8.0.0",
"node-gyp": "^10.1.0",
"node-addon-api": "^8.5.0",
"node-gyp": "^11.5.0",
"tqdm": "^2.0.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.0.0"
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0"
},
"devDependencies": {
"@faker-js/faker": "^8.4.1",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"@faker-js/faker": "^10.1.0",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^5.1.3",
"nyc": "^17.0.0",
"prettier": "^3.3.2",
"ts-jest": "^29.1.5",
"typescript": "^5.1.6"
"eslint-config-prettier": "^8.10.2",
"eslint-plugin-prettier": "^5.5.4",
"nyc": "^17.1.0",
"prettier": "^3.6.2",
"ts-jest": "^29.4.5",
"typescript": "^5.9.3"
},

@@ -76,0 +76,0 @@ "jest": {

@@ -99,4 +99,40 @@ node-libzim

## Local Development
### Important Files
`.env` - Set environment variables for local development. Only LIBZIM_VERSION for now
`bindings.gyp` - Node-gyp build configuration file
`src/` - Source code for the Node.js bindings
`test/` - Test cases
### Setup
```bash
git clone git@github.com:openzim/node-libzim.git
cd node-libzim
# Will install dependencies, download libzim binary and build the bindings
npm run install
# Required in order for local binding and tests to work.
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/download/lib/x86_64-linux-gnu
```
### Iterating during development
Make your changes in `src/` and then run:
```bash
node-gyp rebuild --debug -v && npx jest ./test/zim.test.ts
```
### Updating libzim version
To update the libzim version used, change the `LIBZIM_VERSION` variable in
`.env` file to the desired version and run:
```bash
npm run install
```
If you are upgrading libzim from a major version you will need to edit the `bundle-libzim.js` file
and change the `libzim.so*` file names to match the new version.
## License

@@ -103,0 +139,0 @@

@@ -6,8 +6,11 @@ #pragma once

#include <exception>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include "entry.h"
#include "illustration.h"
#include "item.h"
#include "openconfig.h"

@@ -19,11 +22,33 @@ class Archive : public Napi::ObjectWrap<Archive> {

Napi::Env env = info.Env();
Napi::HandleScope scope(env);
if (info.Length() < 1) {
throw Napi::Error::New(info.Env(), "Archive requires arguments filepath");
throw Napi::Error::New(env, "Archive requires arguments filepath");
}
if (!info[0].IsString()) {
throw Napi::TypeError::New(env,
"First argument must be a string filepath.");
}
// Archive(filename: string)
// Archive(filepath: string, config: OpenConfig)
std::string filepath = info[0].As<Napi::String>();
zim::OpenConfig config{};
if (info[1].IsObject()) {
// @note: no bounds checking on info because it returns Undefined when out
// of bounds
auto obj = info[1].As<Napi::Object>();
// Check that the object is an instance of OpenConfig
// TODO(kelvinhammond): Update use of Unwrap everywhere to use
// InstanceOf and GetConstructor pattern
if (OpenConfig::InstanceOf(env, obj)) {
config = OpenConfig::Unwrap(obj)->getInternalConfig();
} else {
throw Napi::TypeError::New(
env, "Second argument must be an instance of OpenConfig.");
}
}
try {
std::string filepath = info[0].ToString();
archive_ = std::make_shared<zim::Archive>(filepath);
archive_ = std::make_shared<zim::Archive>(filepath, config);
} catch (const std::exception &e) {

@@ -84,9 +109,4 @@ throw Napi::Error::New(env, e.what());

try {
// TODO(kelvinhammond): convert this to
// static_cast<std::string>(archive_->getUuid()) This didn't work when
// building because of the below error undefined symbol:
// _ZNK3zim4UuidcvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEv
std::ostringstream out;
out << archive_->getUuid();
return Napi::Value::From(info.Env(), out.str());
return Napi::Value::From(info.Env(),
static_cast<std::string>(archive_->getUuid()));
} catch (const std::exception &err) {

@@ -118,3 +138,2 @@ throw Napi::Error::New(info.Env(), err.what());

auto env = info.Env();
Napi::HandleScope scope(env);
auto res = Napi::Array::New(env);

@@ -132,10 +151,37 @@ size_t idx = 0;

Napi::Value getIllustrationItem(const Napi::CallbackInfo &info) {
auto env = info.Env();
try {
if (info.Length() > 0) {
// getIllustrationItem()
if (info.Length() < 1) {
return Item::New(env, archive_->getIllustrationItem());
}
// getIllustrationItem(size: number)
if (info[0].IsNumber()) {
auto size = static_cast<unsigned int>(info[0].ToNumber().Uint32Value());
return Item::New(info.Env(), archive_->getIllustrationItem(size));
return Item::New(env, archive_->getIllustrationItem(size));
}
return Item::New(info.Env(), archive_->getIllustrationItem());
/// getIllustration(illusInfo: object)
if (info[0].IsObject()) {
auto obj = info[0].As<Napi::Object>();
// getIllustrationItem(illusInfo: IllustrationInfo)
if (IllustrationInfo::InstanceOf(env, obj)) {
auto illusInfo =
IllustrationInfo::Unwrap(obj)->getInternalIllustrationInfo();
return Item::New(env, archive_->getIllustrationItem(illusInfo));
}
// getIllustrationItem(illusInfo: object)
auto illusInfo = IllustrationInfo::infoFrom(obj);
return Item::New(env, archive_->getIllustrationItem(illusInfo));
}
throw Napi::TypeError::New(
env,
"getIllustrationItem expects no arguments, a number size, or an "
"IllustrationInfo object.");
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
throw Napi::Error::New(env, err.what());
}

@@ -145,6 +191,8 @@ }

Napi::Value getIllustrationSizes(const Napi::CallbackInfo &info) {
// Warn: Deprecated, use illustrationSizes property instead.
std::cerr << "Warning: getIllustrationSizes() is deprecated, use "
"illustrationSizes property instead."
<< std::endl;
auto env = info.Env();
try {
auto env = info.Env();
Napi::HandleScope scope(env);
// returns a native Set object

@@ -159,20 +207,48 @@ auto SetConstructor = env.Global().Get("Set").As<Napi::Function>();

} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
throw Napi::Error::New(env, err.what());
}
}
Napi::Value getIllustrationInfos(const Napi::CallbackInfo &info) {
auto env = info.Env();
// getIllustrationInfos(w: number, h: number, minScale: number)
if (info.Length() >= 3) {
auto w = info[0].ToNumber().Uint32Value();
auto h = info[1].ToNumber().Uint32Value();
auto minScale = info[2].ToNumber().FloatValue();
auto infos = archive_->getIllustrationInfos(w, h, minScale);
auto array = Napi::Array::New(env, infos.size());
for (size_t i = 0; i < infos.size(); i++) {
array.Set(i, IllustrationInfo::New(env, infos[i]));
}
return array;
}
// getIllustrationInfos()
auto infos = archive_->getIllustrationInfos();
auto array = Napi::Array::New(env, infos.size());
for (size_t i = 0; i < infos.size(); i++) {
array.Set(i, IllustrationInfo::New(env, infos[i]));
}
return array;
}
Napi::Value getEntryByPath(const Napi::CallbackInfo &info) {
auto env = info.Env();
try {
if (info[0].IsNumber()) {
auto &&idx = info[0].ToNumber();
return Entry::New(info.Env(), archive_->getEntryByPath(idx));
return Entry::New(env, archive_->getEntryByPath(idx));
} else if (info[0].IsString()) {
auto &&path = info[0].ToString();
return Entry::New(info.Env(), archive_->getEntryByPath(path));
return Entry::New(env, archive_->getEntryByPath(path));
}
throw Napi::Error::New(
info.Env(), "Entry index must be a string (path) or number (index).");
env, "Entry index must be a string (path) or number (index).");
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
throw Napi::Error::New(env, err.what());
}

@@ -396,8 +472,8 @@ }

Napi::Value checkIntegrity(const Napi::CallbackInfo &info) {
auto env = info.Env();
try {
auto env = info.Env();
const auto &&checkType = IntegrityCheck::symbolToEnum(env, info[0]);
return Napi::Value::From(env, archive_->checkIntegrity(checkType));
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
throw Napi::Error::New(env, err.what());
}

@@ -422,31 +498,2 @@ }

Napi::Value getClusterCacheMaxSize(const Napi::CallbackInfo &info) {
try {
return Napi::Value::From(info.Env(), archive_->getClusterCacheMaxSize());
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
}
}
Napi::Value getClusterCacheCurrentSize(const Napi::CallbackInfo &info) {
try {
return Napi::Value::From(info.Env(),
archive_->getClusterCacheCurrentSize());
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
}
}
void setClusterCacheMaxSize(const Napi::CallbackInfo &info) {
try {
if (!info[0].IsNumber()) {
throw Napi::Error::New(info.Env(), "Expected a number");
}
auto nbClusters = info[0].As<Napi::Number>().Uint32Value();
archive_->setClusterCacheMaxSize(nbClusters);
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
}
}
Napi::Value getDirentCacheMaxSize(const Napi::CallbackInfo &info) {

@@ -471,4 +518,5 @@ try {

try {
if (!info[0].IsNumber()) {
throw Napi::Error::New(info.Env(), "Expected a number");
if (info.Length() < 1 || !info[0].IsNumber()) {
throw Napi::TypeError::New(info.Env(),
"setDirentCacheMaxSize expects a number");
}

@@ -482,26 +530,4 @@ auto nbDirents = info[0].As<Napi::Number>().Uint32Value();

Napi::Value getDirentLookupCacheMaxSize(const Napi::CallbackInfo &info) {
try {
return Napi::Value::From(info.Env(),
archive_->getDirentLookupCacheMaxSize());
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
}
}
void setDirentLookupCacheMaxSize(const Napi::CallbackInfo &info) {
try {
if (!info[0].IsNumber()) {
throw Napi::Error::New(info.Env(), "Expected a number");
}
auto nbRanges = info[0].As<Napi::Number>().Uint32Value();
archive_->setDirentLookupCacheMaxSize(nbRanges);
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
}
}
static Napi::Value validate(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
try {

@@ -538,3 +564,2 @@ if (info.Length() < 2) {

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -557,2 +582,6 @@ env, "Archive",

"illustrationSizes"),
InstanceMethod<&Archive::getIllustrationInfos>(
"getIllustrationInfos"),
InstanceAccessor<&Archive::getIllustrationInfos>(
"illustrationInfos"),
InstanceMethod<&Archive::getEntryByPath>("getEntryByPath"),

@@ -580,8 +609,2 @@ InstanceMethod<&Archive::getEntryByTitle>("getEntryByTitle"),

InstanceMethod<&Archive::checkIntegrity>("checkIntegrity"),
InstanceMethod<&Archive::getClusterCacheMaxSize>(
"getClusterCacheMaxSize"),
InstanceMethod<&Archive::getClusterCacheCurrentSize>(
"getClusterCacheCurrentSize"),
InstanceMethod<&Archive::setClusterCacheMaxSize>(
"setClusterCacheMaxSize"),
InstanceMethod<&Archive::getDirentCacheMaxSize>(

@@ -593,6 +616,2 @@ "getDirentCacheMaxSize"),

"setDirentCacheMaxSize"),
InstanceMethod<&Archive::getDirentLookupCacheMaxSize>(
"getDirentLookupCacheMaxSize"),
InstanceMethod<&Archive::setDirentLookupCacheMaxSize>(
"setDirentLookupCacheMaxSize"),
InstanceAccessor<&Archive::isMultiPart>("isMultiPart"),

@@ -599,0 +618,0 @@ InstanceAccessor<&Archive::hasNewNamespaceScheme>(

@@ -15,9 +15,7 @@ #pragma once

explicit Blob(const Napi::CallbackInfo &info)
: Napi::ObjectWrap<Blob>(info), blob_{std::make_shared<zim::Blob>()} {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
if (info[0].IsExternal()) { // handle internal zim::Blob
blob_ = std::make_shared<zim::Blob>(
*info[0].As<Napi::External<zim::Blob>>().Data());
: Napi::ObjectWrap<Blob>(info), blob_{} {
if (info[0].IsExternal()) {
// handle internal zim::Blob
// Copy blob (shared_ptr) from external
blob_ = zim::Blob(*info[0].As<Napi::External<zim::Blob>>().Data());
} else if (info.Length() > 0) { // use refcontent_ and copy content

@@ -42,4 +40,4 @@ // TODO(kelvinhammond): avoid copying content somehow in certain scenarios

memcpy(data.get(), buf.Data(), size);
} else { // all others toString()
auto str = info[0].ToString().Utf8Value(); // coerce to string
} else if (info[0].IsString()) { // all others toString()
auto str = info[0].As<Napi::String>().Utf8Value(); // coerce to string
size = str.size();

@@ -49,5 +47,9 @@ data = std::shared_ptr<char>(new char[size],

memcpy(data.get(), str.c_str(), size);
} else {
throw Napi::Error::New(
info.Env(),
"Blob constructor expects an ArrayBuffer, Buffer, or String");
}
blob_ = std::make_shared<zim::Blob>(data, size); // blob takes ownership
blob_ = zim::Blob(data, size); // blob takes ownership
}

@@ -58,4 +60,3 @@ }

auto external = Napi::External<zim::Blob>::New(env, &blob);
auto &constructor = env.GetInstanceData<ModuleConstructors>()->blob;
return constructor.New({external});
return GetConstructor(env).New({external});
}

@@ -66,3 +67,3 @@

// TODO(kelvinhammond): find a way to have a readonly buffer in NodeJS
return Napi::Buffer<char>::Copy(info.Env(), blob_->data(), blob_->size());
return Napi::Buffer<char>::Copy(info.Env(), blob_.data(), blob_.size());
} catch (const std::exception &err) {

@@ -75,3 +76,3 @@ throw Napi::Error::New(info.Env(), err.what());

try {
return Napi::Value::From(info.Env(), (std::string)*blob_);
return Napi::Value::From(info.Env(), (std::string)blob_);
} catch (const std::exception &err) {

@@ -84,3 +85,3 @@ throw Napi::Error::New(info.Env(), err.what());

try {
return Napi::Value::From(info.Env(), blob_->size());
return Napi::Value::From(info.Env(), blob_.size());
} catch (const std::exception &err) {

@@ -91,5 +92,17 @@ throw Napi::Error::New(info.Env(), err.what());

static bool InstanceOf(Napi::Env env, Napi::Value value) {
if (!value.IsObject()) {
return false;
}
Napi::Object obj = value.As<Napi::Object>();
Napi::FunctionReference &constructor = GetConstructor(env);
return obj.InstanceOf(constructor.Value());
}
static Napi::FunctionReference &GetConstructor(Napi::Env env) {
return env.GetInstanceData<ModuleConstructors>()->blob;
}
static void Init(Napi::Env env, Napi::Object exports,
ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =

@@ -108,6 +121,6 @@ DefineClass(env, "Blob",

// internal module methods
std::shared_ptr<zim::Blob> blob() const { return blob_; }
const zim::Blob &blob() const { return blob_; }
private:
std::shared_ptr<zim::Blob> blob_;
zim::Blob blob_;
};

@@ -20,2 +20,4 @@ #pragma once

Napi::FunctionReference archive;
Napi::FunctionReference openConfig;
Napi::FunctionReference illustrationInfo;
Napi::FunctionReference entry;

@@ -64,3 +66,2 @@ Napi::FunctionReference item;

env.GetInstanceData<ModuleConstructors>()->compressionMap;
Napi::HandleScope scope(env);
for (const auto& [bit, symbolRef] : compressionMap) {

@@ -76,4 +77,2 @@ if (!symbolRef.IsEmpty() && symbolRef.Value() == value) {

ModuleConstructors& constructors) {
Napi::HandleScope scope(env);
constexpr auto attrs =

@@ -113,3 +112,2 @@ static_cast<napi_property_attributes>(napi_default | napi_enumerable);

env.GetInstanceData<ModuleConstructors>()->integrityCheckMap;
Napi::HandleScope scope(env);
for (const auto& [bit, symbolRef] : integrityCheckMap) {

@@ -125,4 +123,2 @@ if (!symbolRef.IsEmpty() && symbolRef.Value() == value) {

ModuleConstructors& constructors) {
Napi::HandleScope scope(env);
constexpr auto attrs =

@@ -129,0 +125,0 @@ static_cast<napi_property_attributes>(napi_default | napi_enumerable);

@@ -9,3 +9,5 @@ #pragma once

#include <future>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>

@@ -19,2 +21,71 @@ #include <thread>

/**
* Thread Safe Function wrapper for calling the ContentProvider.feed function
* asynchronously from libzim
*/
class FeedTSFN {
public:
using BlobPtr = zim::Blob;
FeedTSFN() = delete;
FeedTSFN(Napi::Env &env, Napi::Function &feedFunc) {
tsfn_ = TSFN::New(env,
feedFunc, // JavaScript function called asynchronously
"FeedTSFN", // name
0, // max queue size (0 = unlimited).
1, // initial thread count
nullptr); // context
}
~FeedTSFN() { tsfn_.Release(); }
BlobPtr feed() {
try {
DataType promise;
auto future = promise.get_future();
tsfn_.NonBlockingCall(&promise);
return future.get();
} catch (const std::exception &e) {
std::cerr << "FeedTSFN feed() exception: " << e.what() << std::endl;
throw std::runtime_error(std::string("Error in FeedTSFN feed(): ") +
e.what());
}
}
private:
using DataType = std::promise<BlobPtr>;
using Context = void;
static void CallJs(Napi::Env env, Napi::Function callback, Context *context,
DataType *data) {
// Is the JavaScript environment still available to call into, eg. the TSFN
// is not aborted
if (env != nullptr) {
try {
// call feed(): object
auto result = callback.Call({});
if (Blob::InstanceOf(env, result)) {
auto blob = Blob::Unwrap(result.As<Napi::Object>())->blob();
// Note: Cannot move, blob could be used in nodejs world still
data->set_value(blob);
} else {
data->set_exception(std::make_exception_ptr(std::runtime_error(
"Expected an object of type Blob from feed()")));
}
} catch (const std::exception &e) {
data->set_exception(std::make_exception_ptr(e));
}
} else {
data->set_exception(std::make_exception_ptr(
std::runtime_error("Environment is shut down")));
}
}
using TSFN = Napi::TypedThreadSafeFunction<Context, DataType, CallJs>;
private:
TSFN tsfn_;
};
/**
* Wraps the js world ObjectWrap and Objects to a proper content provider for

@@ -25,5 +96,4 @@ * use with libzim

public:
explicit ContentProviderWrapper(Napi::Env env, const Napi::Object &provider)
: MAIN_THREAD_ID{} {
MAIN_THREAD_ID = std::this_thread::get_id();
explicit ContentProviderWrapper(Napi::Env env, const Napi::Object &provider) {
size_ = parseSize(provider.Get("size"));

@@ -35,12 +105,5 @@ if (!provider.Get("feed").IsFunction()) {

auto feedFunc = provider.Get("feed").As<Napi::Function>();
feed_ = Napi::Persistent(feedFunc);
size_ = parseSize(provider.Get("size"));
provider_ = Napi::Persistent(provider);
tsfn_ = Napi::ThreadSafeFunction::New(env, feedFunc,
"getContentProvider.feed", 0, 1);
feedTSFN_ = std::make_unique<FeedTSFN>(env, feedFunc);
}
~ContentProviderWrapper() { tsfn_.Release(); }
zim::size_type getSize() const override { return size_; }

@@ -67,42 +130,9 @@

zim::Blob feed() override {
if (MAIN_THREAD_ID == std::this_thread::get_id()) {
// on main thread for some reason, do it here
auto blobObj = feed_.Call(provider_.Value(), {});
if (!blobObj.IsObject()) {
throw std::runtime_error("ContentProvider.feed must return a blob");
}
auto blob = Napi::ObjectWrap<Blob>::Unwrap(blobObj.ToObject());
return *(blob->blob());
}
zim::Blob feed() override { return feedTSFN_->feed(); }
// called from a thread
std::promise<zim::Blob> promise;
auto future = promise.get_future();
auto callback = [&promise, this](Napi::Env env, Napi::Function feedFunc) {
auto blobObj = feedFunc.Call(provider_.Value(), {});
if (!blobObj.IsObject()) {
throw std::runtime_error("ContentProvider.feed must return a blob");
}
auto blob = Napi::ObjectWrap<Blob>::Unwrap(blobObj.ToObject());
promise.set_value(*(blob->blob()));
};
auto status = tsfn_.BlockingCall(callback);
if (status != napi_ok) {
throw std::runtime_error("Error calling ThreadSafeFunction");
}
return future.get();
}
private:
// track the main thread
std::thread::id MAIN_THREAD_ID;
// js world reference, could be an ObjectWrap provider or custom js object
Napi::ObjectReference provider_;
Napi::FunctionReference feed_;
Napi::ThreadSafeFunction tsfn_;
zim::size_type size_;
// Unique pointer so that it isn't copied and the destructor will only be
// called once
std::unique_ptr<FeedTSFN> feedTSFN_;
};

@@ -152,2 +182,7 @@

Napi::Value getSize(const Napi::CallbackInfo &info) {
if (!provider_) {
throw Napi::Error::New(
info.Env(), "StringProvider has been moved and is no longer valid.");
}
try {

@@ -161,4 +196,8 @@ return Napi::Value::From(info.Env(), provider_->getSize());

Napi::Value feed(const Napi::CallbackInfo &info) {
if (!provider_) {
throw Napi::Error::New(
info.Env(), "StringProvider has been moved and is no longer valid.");
}
try {
// TODO(kelvinhammond): need a way to move this to avoid copying
auto blob = provider_->feed();

@@ -171,5 +210,17 @@ return Blob::New(info.Env(), blob);

static bool InstanceOf(Napi::Env env, Napi::Value value) {
if (!value.IsObject()) {
return false;
}
Napi::Object obj = value.As<Napi::Object>();
Napi::FunctionReference &constructor = GetConstructor(env);
return obj.InstanceOf(constructor.Value());
}
static Napi::FunctionReference &GetConstructor(Napi::Env env) {
return env.GetInstanceData<ModuleConstructors>()->stringProvider;
}
static void Init(Napi::Env env, Napi::Object exports,
ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =

@@ -187,2 +238,7 @@ DefineClass(env, "StringProvider",

// Internal use only
std::unique_ptr<zim::writer::StringProvider> &&unwrapProvider() {
return std::move(provider_);
}
private:

@@ -232,2 +288,7 @@ std::unique_ptr<zim::writer::StringProvider> provider_;

Napi::Value getSize(const Napi::CallbackInfo &info) {
if (!provider_) {
throw Napi::Error::New(
info.Env(), "FileProvider has been moved and is no longer valid.");
}
try {

@@ -241,4 +302,8 @@ return Napi::Value::From(info.Env(), provider_->getSize());

Napi::Value feed(const Napi::CallbackInfo &info) {
if (!provider_) {
throw Napi::Error::New(
info.Env(), "FileProvider has been moved and is no longer valid.");
}
try {
// TODO(kelvinhammond): need a way to move this to avoid copying
auto blob = provider_->feed();

@@ -251,5 +316,17 @@ return Blob::New(info.Env(), blob);

static bool InstanceOf(Napi::Env env, Napi::Value value) {
if (!value.IsObject()) {
return false;
}
Napi::Object obj = value.As<Napi::Object>();
Napi::FunctionReference &constructor = GetConstructor(env);
return obj.InstanceOf(constructor.Value());
}
static Napi::FunctionReference &GetConstructor(Napi::Env env) {
return env.GetInstanceData<ModuleConstructors>()->fileProvider;
}
static void Init(Napi::Env env, Napi::Object exports,
ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =

@@ -267,2 +344,7 @@ DefineClass(env, "FileProvider",

// Internal use only
std::unique_ptr<zim::writer::FileProvider> &&unwrapProvider() {
return std::move(provider_);
}
private:

@@ -269,0 +351,0 @@ std::unique_ptr<zim::writer::FileProvider> provider_;

#pragma once
#include <napi.h>
#include <zim/illustration.h>
#include <zim/writer/creator.h>

@@ -8,2 +9,4 @@

#include <functional>
#include <iostream>
#include <map>
#include <memory>

@@ -14,2 +17,3 @@ #include <string>

#include "common.h"
#include "illustration.h"
#include "writerItem.h"

@@ -29,3 +33,10 @@

void Execute() override { creator_->finishZimCreation(); }
void Execute() override {
try {
creator_->finishZimCreation();
} catch (const std::exception &e) {
std::cerr << "Error: finishZimCreation failed: " << e.what() << std::endl;
SetError(e.what());
}
}

@@ -60,3 +71,11 @@ void OnOK() override {

void Execute() override { creator_->addItem(item_); }
void Execute() override {
try {
creator_->addItem(item_);
} catch (const std::exception &e) {
std::cerr << "Error: AddItemAsyncWorker failed: " << e.what()
<< std::endl;
SetError(e.what());
}
}

@@ -170,12 +189,7 @@ void OnOK() override {

const auto &stringItem =
env.GetInstanceData<ModuleConstructors>()->stringItem.Value();
const auto &fileItem =
env.GetInstanceData<ModuleConstructors>()->fileItem.Value();
std::shared_ptr<zim::writer::Item> item{};
auto obj = info[0].ToObject();
if (obj.InstanceOf(stringItem)) {
auto obj = info[0].As<Napi::Object>();
if (StringItem::InstanceOf(env, obj)) {
item = Napi::ObjectWrap<StringItem>::Unwrap(obj)->getItem();
} else if (obj.InstanceOf(fileItem)) {
} else if (FileItem::InstanceOf(env, obj)) {
item = Napi::ObjectWrap<FileItem>::Unwrap(obj)->getItem();

@@ -226,5 +240,24 @@ } else {

auto content = info[1];
if (content.IsObject()) { // content provider
std::unique_ptr<zim::writer::ContentProvider> provider =
std::make_unique<ContentProviderWrapper>(env, content.ToObject());
if (content.IsObject()) {
// addMetadata(name: string, content: ContentProvider, [mimetype])
auto obj = content.As<Napi::Object>();
std::unique_ptr<zim::writer::ContentProvider> provider{nullptr};
// Determine the type of ContentProvider
if (StringProvider::InstanceOf(env, content)) {
auto wrapped = Napi::ObjectWrap<StringProvider>::Unwrap(obj);
provider = wrapped->unwrapProvider();
} else if (FileProvider::InstanceOf(env, content)) {
auto wrapped = Napi::ObjectWrap<FileProvider>::Unwrap(obj);
provider = wrapped->unwrapProvider();
} else {
// Fallback to generic ContentProviderWrapper
provider = std::make_unique<ContentProviderWrapper>(env, obj);
}
if (provider == nullptr) {
throw Napi::Error::New(
env, "addMetadata failed to create ContentProvider from object");
}
if (info.Length() > 2) { // preserves default argument

@@ -237,3 +270,3 @@ auto mimetype = info[2].ToString().Utf8Value();

}
} else { // string version
} else { // addMetadata(name: string, content: string, [mimetype])
auto str = content.ToString().Utf8Value();

@@ -248,2 +281,3 @@ if (info.Length() > 2) {

} catch (const std::exception &err) {
std::cerr << "Error: addMetadata failed: " << err.what() << std::endl;
throw Napi::Error::New(info.Env(), err.what());

@@ -256,11 +290,36 @@ }

auto env = info.Env();
auto size = info[0].ToNumber().Uint32Value();
auto content = info[1];
if (content.IsObject()) {
std::unique_ptr<zim::writer::ContentProvider> provider =
std::make_unique<ContentProviderWrapper>(env, content.ToObject());
creator_->addIllustration(size, std::move(provider));
// Inline template function to handle both size and IllustrationInfo
const auto addIllusWithContent = [&](auto &opt1) {
auto content = info[1];
if (content.IsObject()) {
std::unique_ptr<zim::writer::ContentProvider> provider =
std::make_unique<ContentProviderWrapper>(env, content.ToObject());
creator_->addIllustration(opt1, std::move(provider));
} else {
auto str = content.ToString().Utf8Value();
creator_->addIllustration(opt1, str);
}
};
auto arg0 = info[0];
if (arg0.IsNumber()) {
auto size = arg0.ToNumber().Uint32Value();
addIllusWithContent(size);
} else if (arg0.IsObject()) {
// Parse as IllustrationInfo
auto obj = arg0.ToObject();
// getIllustrationItem(illusInfo: IllustrationInfo)
// getIllustrationItem(illusInfo: object)
auto illusInfo =
IllustrationInfo::InstanceOf(env, obj)
? IllustrationInfo::Unwrap(obj)->getInternalIllustrationInfo()
: IllustrationInfo::infoFrom(obj);
addIllusWithContent(illusInfo);
} else {
auto str = content.ToString().Utf8Value();
creator_->addIllustration(size, str);
throw Napi::Error::New(
env,
"addIllustration first argument must be size[number] or "
"IllustrationInfo[object]");
}

@@ -327,3 +386,2 @@ } catch (const std::exception &err) {

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -330,0 +388,0 @@ env, "Creator",

@@ -96,3 +96,2 @@ #pragma once

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -99,0 +98,0 @@ env, "Entry",

@@ -0,1 +1,5 @@

export declare function getClusterCacheMaxSize(): number;
export declare function getClusterCacheCurrentSize(): number;
export declare function setClusterCacheMaxSize(nbClusters: number): void;
export class IntegrityCheck {

@@ -107,3 +111,6 @@ static CHECKSUM: symbol;

): void;
addIllustration(size: number, content: string | ContentProvider): void;
addIllustration(
sizeOrInfo: number | IIllustrationInfo,
content: string | ContentProvider,
): void;
addRedirection(

@@ -150,4 +157,31 @@ path: string,

export interface IIllustrationInfo {
width?: number;
height?: number;
scale?: number;
extraAttributes?: Record<string, string>;
}
export class IllustrationInfo implements IIllustrationInfo {
constructor(info?: IIllustrationInfo | IllustrationInfo);
get width(): number;
get height(): number;
get scale(): number;
get extraAttributes(): Record<string, string>;
asMetadataItemName(): string;
static fromMetadataItemName(name: string): IllustrationInfo;
}
export class OpenConfig {
constructor();
preloadXapianDb(preload: boolean): this;
preloadDirentRanges(nbRanges: number): this;
get m_preloadXapianDb(): boolean;
get m_preloadDirentRanges(): number;
}
export class Archive {
constructor(filepath: string);
constructor(filepath: string, config?: OpenConfig);
get filename(): string;

@@ -163,4 +197,10 @@ get filesize(): number | bigint;

get metadataKeys(): string[];
getIllustrationItem(size: number): Item;
getIllustrationItem(sizeOrInfo?: number | IIllustrationInfo): Item;
get illustrationSizes(): Set<number>;
getIllustrationInfos(
width?: number,
height?: number,
minScale?: number,
): IllustrationInfo[];
get illustrationInfos(): IllustrationInfo[];
getEntryByPath(path_or_idx: string | number): Entry;

@@ -188,10 +228,5 @@ getEntryByTitle(title_or_idx: string | number): Entry;

get hasNewNamespaceScheme(): boolean;
getClusterCacheMaxSize(): number;
getClusterCacheCurrentSize(): number;
setClusterCacheMaxSize(nbClusters: number): void;
getDirentCacheMaxSize(): number;
getDirentCacheCurrentSize(): number;
setDirentCacheMaxSize(nbDirents: number): void;
getDirentLookupCacheMaxSize(): number;
setDirentLookupCacheMaxSize(nbRanges: number): void;

@@ -198,0 +233,0 @@ static validate(zimPath: string, checksToRun: symbol[]): boolean; // list of IntegrityCheck

@@ -5,5 +5,7 @@ import bindings from "bindings";

Archive,
OpenConfig,
Entry,
IntegrityCheck,
Compression,
IllustrationInfo,
Blob,

@@ -18,2 +20,5 @@ Searcher,

FileItem,
getClusterCacheMaxSize,
getClusterCacheCurrentSize,
setClusterCacheMaxSize,
} = bindings("zim_binding");

@@ -15,3 +15,2 @@ #pragma once

Napi::Env env = info.Env();
Napi::HandleScope scope(env);

@@ -112,6 +111,8 @@ if (!info[0].IsExternal()) {

auto env = info.Env();
const auto valPair = item_->getDirectAccessInformation();
const auto dai = item_->getDirectAccessInformation();
auto res = Napi::Object::New(env);
res["filename"] = Napi::Value::From(env, valPair.first);
res["offset"] = Napi::Value::From(env, valPair.second);
res["filename"] = Napi::Value::From(env, dai.filename);
res["offset"] = Napi::Value::From(env, dai.offset);
res["isValid"] = Napi::Value::From(env, dai.isValid());
res.Freeze();
return res;

@@ -133,3 +134,2 @@ } catch (const std::exception &err) {

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =

@@ -136,0 +136,0 @@ DefineClass(env, "Item",

@@ -10,3 +10,5 @@

#include "entry.h"
#include "illustration.h"
#include "item.h"
#include "openconfig.h"
#include "search.h"

@@ -30,2 +32,4 @@ #include "suggestion.h"

Archive::Init(env, exports, *constructors);
OpenConfig::Init(env, exports, *constructors);
IllustrationInfo::Init(env, exports, *constructors);

@@ -50,2 +54,24 @@ Searcher::Init(env, exports, *constructors);

// Extra helper functions from libzim
exports.Set("getClusterCacheMaxSize",
Napi::Function::New(env, [](const Napi::CallbackInfo &info) {
return Napi::Value::From(info.Env(),
zim::getClusterCacheMaxSize());
}));
exports.Set("getClusterCacheCurrentSize",
Napi::Function::New(env, [](const Napi::CallbackInfo &info) {
return Napi::Value::From(info.Env(),
zim::getClusterCacheCurrentSize());
}));
exports.Set("setClusterCacheMaxSize",
Napi::Function::New(env, [](const Napi::CallbackInfo &info) {
if (info.Length() < 1 || !info[0].IsNumber()) {
throw Napi::TypeError::New(
info.Env(),
"First argument must be a number for max size.");
}
auto size = info[0].As<Napi::Number>().Int64Value();
zim::setClusterCacheMaxSize(size);
}));
return exports;

@@ -52,0 +78,0 @@ }

@@ -121,3 +121,2 @@ #pragma once

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -148,5 +147,2 @@ env, "Query",

: Napi::ObjectWrap<SearchIterator>(info), searchIterator_{} {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
if (info[0].IsExternal()) {

@@ -238,3 +234,2 @@ searchIterator_ =

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -266,3 +261,2 @@ env, "SearchIterator",

Napi::Env env = info.Env();
Napi::HandleScope scope(env);

@@ -333,3 +327,2 @@ if (!info[0].IsExternal()) {

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =

@@ -355,7 +348,4 @@ DefineClass(env, "SearchResultSet",

: Napi::ObjectWrap<Search>(info), search_{nullptr} {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
if (!info[0].IsExternal()) {
throw Napi::Error::New(env, "Search must be created internally.");
throw Napi::Error::New(info.Env(), "Search must be created internally.");
}

@@ -402,3 +392,2 @@

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -424,3 +413,2 @@ env, "Search",

Napi::Env env = info.Env();
Napi::HandleScope scope(env);

@@ -510,3 +498,2 @@ if (info[0].IsArray()) {

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =

@@ -513,0 +500,0 @@ DefineClass(env, "Searcher",

@@ -20,5 +20,2 @@ #pragma once

: Napi::ObjectWrap<SuggestionIterator>(info), iterator_{} {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
if (info[0].IsExternal()) {

@@ -79,3 +76,2 @@ iterator_ = *info[0].As<Napi::External<decltype(iterator_)>>().Data();

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -104,3 +100,2 @@ env, "SuggestionIterator",

Napi::Env env = info.Env();
Napi::HandleScope scope(env);

@@ -169,3 +164,2 @@ if (!info[0].IsExternal()) {

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =

@@ -192,3 +186,2 @@ DefineClass(env, "SuggestionResultSet",

Napi::Env env = info.Env();
Napi::HandleScope scope(env);

@@ -240,3 +233,2 @@ if (!info[0].IsExternal()) {

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -264,3 +256,2 @@ env, "SuggestionSearch",

Napi::Env env = info.Env();
Napi::HandleScope scope(env);

@@ -350,3 +341,2 @@ // TODO(kelvinhammond): Ask about support for suggestions from multiple

ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -353,0 +343,0 @@ env, "SuggestionSearcher",

@@ -10,2 +10,3 @@ #pragma once

#include <future>
#include <iostream>
#include <memory>

@@ -16,2 +17,3 @@ #include <optional>

#include <thread>
#include <utility>

@@ -95,13 +97,151 @@ #include "blob.h"

/**
* Wraps a the getIndexData JS function to a ThreadSafeFunction
*/
class GetIndexDataTSFN {
public:
using IndexDataWrapperPtr = std::shared_ptr<IndexDataWrapper>;
GetIndexDataTSFN() = delete;
GetIndexDataTSFN(Napi::Env &env, Napi::Function &indexDataFunc) {
tsfn_ =
TSFN::New(env,
indexDataFunc, // JavaScript function called asynchronously
"GetIndexDataTSFN", // name
0, // max queue size (0 = unlimited).
1, // initial thread count
nullptr); // context
}
~GetIndexDataTSFN() { tsfn_.Release(); }
IndexDataWrapperPtr getIndexData() {
try {
DataType promise;
auto future = promise.get_future();
tsfn_.NonBlockingCall(&promise);
return future.get();
} catch (const std::exception &e) {
std::cerr << "GetIndexDataTSFN getIndexData() exception: " << e.what()
<< std::endl;
throw std::runtime_error(
std::string("Error in GetIndexDataTSFN getIndexData(): ") + e.what());
}
}
private:
using DataType = std::promise<IndexDataWrapperPtr>;
using Context = void;
static void CallJs(Napi::Env env, Napi::Function callback, Context *context,
DataType *data) {
// Is the JavaScript environment still available to call into, eg. the TSFN
// is not aborted
if (env != nullptr) {
try {
// call getIndexData(): object
auto result = callback.Call({});
if (result.IsObject()) {
auto indexData =
std::make_shared<IndexDataWrapper>(result.As<Napi::Object>());
data->set_value(indexData);
} else {
data->set_exception(std::make_exception_ptr(
std::runtime_error("Expected an object from getIndexData")));
}
} catch (const std::exception &e) {
data->set_exception(std::make_exception_ptr(e));
}
} else {
data->set_exception(std::make_exception_ptr(
std::runtime_error("Environment is shut down")));
}
}
using TSFN = Napi::TypedThreadSafeFunction<Context, DataType, CallJs>;
private:
TSFN tsfn_;
};
/**
* Wraps a the getContentProvider JS function to a ThreadSafeFunction
*/
class GetContentProviderTSFN {
public:
using ContentProviderWrapperPtr = std::unique_ptr<ContentProviderWrapper>;
GetContentProviderTSFN() = delete;
GetContentProviderTSFN(Napi::Env &env, Napi::Function &providerFunc) {
tsfn_ =
TSFN::New(env,
providerFunc, // JavaScript function called asynchronously
"GetContentProviderTSFN", // name
0, // max queue size (0 = unlimited).
1, // initial thread count
nullptr); // context
}
~GetContentProviderTSFN() { tsfn_.Release(); }
ContentProviderWrapperPtr getContentProvider() {
try {
DataType promise;
auto future = promise.get_future();
tsfn_.NonBlockingCall(&promise);
return future.get();
} catch (const std::exception &e) {
std::cerr << "GetContentProviderTSFN getContentProvider() exception: "
<< e.what() << std::endl;
throw std::runtime_error(
std::string(
"Error in GetContentProviderTSFN getContentProvider(): ") +
e.what());
}
}
private:
using DataType = std::promise<ContentProviderWrapperPtr>;
using Context = void;
static void CallJs(Napi::Env env, Napi::Function callback, Context *context,
DataType *data) {
// Is the JavaScript environment still available to call into, eg. the
// TSFN is not aborted
if (env != nullptr) {
try {
// call getContentProvider(): object
auto result = callback.Call({});
if (result.IsObject()) {
auto provider = std::make_unique<ContentProviderWrapper>(
env, result.As<Napi::Object>());
data->set_value(std::move(provider));
} else {
data->set_exception(std::make_exception_ptr(std::runtime_error(
"Expected an object from getContentProvider")));
}
} catch (const std::exception &e) {
data->set_exception(std::make_exception_ptr(e));
}
} else {
data->set_exception(std::make_exception_ptr(
std::runtime_error("Environment is shut down")));
}
}
using TSFN = Napi::TypedThreadSafeFunction<Context, DataType, CallJs>;
private:
TSFN tsfn_;
};
/**
* Wraps a JS World Item to a zim::writer::Item
*
* NOTE: should be initialized on the main thread
* NOTE: MUST BE initialized on the main thread
*/
class ItemWrapper : public zim::writer::Item {
public:
ItemWrapper(Napi::Env env, Napi::Object item)
: MAIN_THREAD_ID{}, item_{}, hasIndexDataImpl_{false} {
MAIN_THREAD_ID = std::this_thread::get_id();
item_ = Napi::Persistent(item);
ItemWrapper(Napi::Env env, Napi::Object item) {
path_ = item.Get("path").ToString();

@@ -124,5 +264,4 @@ title_ = item.Get("title").ToString();

auto indexDataFunc = indexDataFuncValue.As<Napi::Function>();
indexDataFunc_ = Napi::Persistent(indexDataFunc);
indexDataTSNF_ = Napi::ThreadSafeFunction::New(
env, indexDataFunc, "ItemWrapper.indexData", 0, 1);
getIndexDataTSFN_ =
std::make_unique<GetIndexDataTSFN>(env, indexDataFunc);
}

@@ -135,14 +274,6 @@

auto providerFunc = providerFuncValue.As<Napi::Function>();
contentProviderFunc_ = Napi::Persistent(providerFunc);
contentProviderTSNF_ = Napi::ThreadSafeFunction::New(
env, providerFunc, "ItemWrapper.contentProvider", 5, 1);
getContentProviderTSFN_ =
std::make_unique<GetContentProviderTSFN>(env, providerFunc);
}
~ItemWrapper() {
if (hasIndexDataImpl_) {
indexDataTSNF_.Release();
}
contentProviderTSNF_.Release();
}
std::string getPath() const override { return path_; }

@@ -162,27 +293,7 @@

if (MAIN_THREAD_ID == std::this_thread::get_id()) {
auto data = indexDataFunc_.Call(item_.Value(), {});
return data.IsObject()
? std::make_shared<IndexDataWrapper>(data.ToObject())
: nullptr;
if (getIndexDataTSFN_ == nullptr) {
throw std::runtime_error("Error: getIndexDataTSFN_ is null");
}
// called from a thread
using IndexDataWrapperPtr = std::shared_ptr<IndexDataWrapper>;
std::promise<IndexDataWrapperPtr> promise;
auto future = promise.get_future();
auto callback = [&promise, this](Napi::Env env, Napi::Function idxFunc) {
auto data = idxFunc.Call(item_.Value(), {});
promise.set_value(
data.IsObject() ? std::make_shared<IndexDataWrapper>(data.ToObject())
: nullptr);
};
auto status = indexDataTSNF_.BlockingCall(callback);
if (status != napi_ok) {
throw std::runtime_error("Error calling indexData ThreadSafeFunction");
}
return future.get();
return getIndexDataTSFN_->getIndexData();
}

@@ -196,49 +307,6 @@

const override {
if (MAIN_THREAD_ID == std::this_thread::get_id()) {
auto env = contentProviderFunc_.Env();
auto provider = contentProviderFunc_.Call(item_.Value(), {});
if (provider.IsObject()) {
return std::make_unique<ContentProviderWrapper>(env,
provider.ToObject());
} else if (provider.IsNull() || provider.IsUndefined()) {
return nullptr;
}
throw std::runtime_error(
"getContentProvider must return an object or null");
}
using ContentProviderWrapperPtr = std::unique_ptr<ContentProviderWrapper>;
std::promise<ContentProviderWrapperPtr> promise;
auto future = promise.get_future();
auto callback = [&promise, this](Napi::Env env,
Napi::Function providerFunc) {
auto provider = providerFunc.Call(item_.Value(), {});
if (provider.IsObject()) {
auto ptr =
std::make_unique<ContentProviderWrapper>(env, provider.ToObject());
promise.set_value(std::move(ptr));
} else if (provider.IsNull() || provider.IsUndefined()) {
promise.set_value(nullptr);
} else {
throw std::runtime_error(
"getContentProvider must return an object or null");
}
};
auto status = contentProviderTSNF_.BlockingCall(callback);
if (status != napi_ok) {
throw std::runtime_error(
"Error calling contentProvider ThreadSafeFunction");
}
return future.get();
return getContentProviderTSFN_->getContentProvider();
}
private:
std::thread::id MAIN_THREAD_ID;
// js world reference, could be an ObjectWrap provider or custom js object
Napi::ObjectReference item_;
std::string path_;

@@ -250,8 +318,9 @@ std::string title_;

Napi::FunctionReference indexDataFunc_;
Napi::ThreadSafeFunction indexDataTSNF_;
Napi::FunctionReference contentProviderFunc_;
Napi::ThreadSafeFunction contentProviderTSNF_;
std::unique_ptr<GetContentProviderTSFN> getContentProviderTSFN_;
std::unique_ptr<GetIndexDataTSFN> getIndexDataTSFN_;
};
/**
* Wraps a zim::writer::StringItem
*/
class StringItem : public Napi::ObjectWrap<StringItem> {

@@ -351,5 +420,13 @@ public:

static bool InstanceOf(Napi::Env env, Napi::Object obj) {
Napi::FunctionReference &constructor = GetConstructor(env);
return obj.InstanceOf(constructor.Value());
}
static Napi::FunctionReference &GetConstructor(Napi::Env env) {
return env.GetInstanceData<ModuleConstructors>()->stringItem;
}
static void Init(Napi::Env env, Napi::Object exports,
ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =

@@ -374,2 +451,5 @@ DefineClass(env, "StringItem",

/**
* Wraps a zim::writer::FileItem
*/
class FileItem : public Napi::ObjectWrap<FileItem> {

@@ -452,5 +532,13 @@ public:

static bool InstanceOf(Napi::Env env, Napi::Object obj) {
Napi::FunctionReference &constructor = GetConstructor(env);
return obj.InstanceOf(constructor.Value());
}
static Napi::FunctionReference &GetConstructor(Napi::Env env) {
return env.GetInstanceData<ModuleConstructors>()->fileItem;
}
static void Init(Napi::Env env, Napi::Object exports,
ModuleConstructors &constructors) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(

@@ -457,0 +545,0 @@ env, "FileItem",

#pragma once
#include <napi.h>
#include <zim/entry.h>
#include <exception>
#include <memory>
#include "entry.h"
class EntryRange : public Napi::ObjectWrap<EntryRange> {
public:
static constexpr const char *ENTRY_RANGE_CONSTRUCTOR_NAME = "EntryRange";
explicit EntryRange(const Napi::CallbackInfo &info)
: Napi::ObjectWrap<EntryRange>(info) {
auto env = info.Env();
if (!info[0].IsExternal()) {
throw Napi::Error::New(
env, "EntryRange must be constructed internally by another class.");
}
if (info[0].IsExternal()) {
try {
entry_ = std::make_shared<zim::Entry>(
*info[0].As<Napi::External<zim::Entry>>().Data());
} catch (const std::exception &err) {
throw Napi::Error::New(env, err.what());
}
}
}
template <typename RangeT>
static Napi::Object New(Napi::Env env, RangeT range) {
Napi::Function iterator = Napi::Function::New(
env, [range](const Napi::CallbackInfo &info) mutable -> Napi::Value {
Napi::Env env = info.Env();
Napi::Object iter = Napi::Object::New(env);
auto it = range.begin();
iter["next"] = Napi::Function::New(
env,
[range,
it](const Napi::CallbackInfo &info) mutable -> Napi::Value {
Napi::Env env = info.Env();
Napi::Object res = Napi::Object::New(env);
if (it != range.end()) {
res["done"] = false;
res["value"] = Entry::New(env, zim::Entry(*it));
it++;
} else {
res["done"] = true;
}
return res;
});
return iter;
});
auto offset = Napi::Function::New(
env, [range](const Napi::CallbackInfo &info) -> Napi::Value {
if (info.Length() < 2) {
throw Napi::Error::New(
info.Env(), "start and maxResults are required for offset.");
}
if (!(info[0].IsNumber() && info[1].IsNumber())) {
throw Napi::Error::New(
info.Env(), "start and maxResults must be of type Number.");
}
auto start = info[0].ToNumber();
auto maxResults = info[1].ToNumber();
return NewEntryRange(info.Env(), range.offset(start, maxResults));
});
auto size = Napi::Value::From(env, range.size());
auto &constructor = env.GetInstanceData<ConstructorsMap>()->at(
ENTRY_RANGE_CONSTRUCTOR_NAME);
return constructor.New({iterator, size, offset});
}
Napi::Value getSize(const Napi::CallbackInfo &info) {
try {
return Napi::Value::From(info.Env(), entry_->getIndex());
} catch (const std::exception &err) {
throw Napi::Error::New(info.Env(), err.what());
}
}
static void Init(Napi::Env env, Napi::Object exports,
ConstructorsMap &constructors) {
Napi::HandleScope scope(env);
Napi::Function func =
DefineClass(env, "EntryRange",
{
InstanceAccessor<&EntryRange::getSize>("size"),
InstanceMethod<&EntryRange::getItem>("getItem"),
});
exports.Set("EntryRange", func);
constructors.insert_or_assign(ENTRY_RANGE_CONSTRUCTOR_NAME,
Napi::Persistent(func));
}
private:
std::shared_ptr<zim::Entry> entry_;
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet