@openzim/libzim
Advanced tools
| #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 |
+1
-1
@@ -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"; |
+43
-8
@@ -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
-0
@@ -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 @@ |
+21
-21
@@ -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": { |
+36
-0
@@ -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 @@ |
+110
-91
@@ -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>( |
+31
-18
@@ -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_; | ||
| }; |
+2
-6
@@ -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); |
+134
-52
@@ -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_; |
+81
-23
| #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", |
+0
-1
@@ -96,3 +96,2 @@ #pragma once | ||
| ModuleConstructors &constructors) { | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = DefineClass( | ||
@@ -99,0 +98,0 @@ env, "Entry", |
+43
-8
@@ -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
-0
@@ -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"); |
+5
-5
@@ -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", |
+26
-0
@@ -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 @@ } |
+1
-14
@@ -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", |
+0
-10
@@ -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", |
+180
-92
@@ -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", |
-106
| #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
188785
9.22%29
3.57%734
10.04%141
34.29%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated