node-addon-api 3.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clang-format +111 -0
- package/.github/workflows/ci.yml +55 -0
- package/.github/workflows/linter.yml +24 -0
- package/.github/workflows/stale.yml +18 -0
- package/.travis.yml +0 -1
- package/CHANGELOG.md +76 -0
- package/README.md +23 -4
- package/doc/addon.md +5 -5
- package/doc/array_buffer.md +16 -0
- package/doc/async_context.md +1 -1
- package/doc/async_worker.md +1 -1
- package/doc/async_worker_variants.md +141 -40
- package/doc/boolean.md +1 -1
- package/doc/checker-tool.md +1 -1
- package/doc/creating_a_release.md +1 -1
- package/doc/error.md +1 -1
- package/doc/escapable_handle_scope.md +1 -1
- package/doc/function.md +2 -2
- package/doc/function_reference.md +1 -1
- package/doc/handle_scope.md +1 -1
- package/doc/hierarchy.md +1 -1
- package/doc/number.md +1 -1
- package/doc/object_lifetime_management.md +1 -1
- package/doc/object_reference.md +1 -1
- package/doc/object_wrap.md +1 -1
- package/doc/prebuild_tools.md +2 -2
- package/doc/property_descriptor.md +3 -3
- package/doc/threadsafe.md +121 -0
- package/doc/threadsafe_function.md +16 -46
- package/doc/typed_threadsafe_function.md +307 -0
- package/doc/version_management.md +2 -2
- package/napi-inl.h +550 -0
- package/napi.h +195 -0
- package/package-support.json +21 -0
- package/package.json +45 -3
- package/tools/README.md +8 -2
- package/tools/clang-format.js +47 -0
- package/doc/Doxyfile +0 -2450
package/napi.h
CHANGED
|
@@ -822,6 +822,11 @@ namespace Napi {
|
|
|
822
822
|
|
|
823
823
|
void* Data(); ///< Gets a pointer to the data buffer.
|
|
824
824
|
size_t ByteLength(); ///< Gets the length of the array buffer in bytes.
|
|
825
|
+
|
|
826
|
+
#if NAPI_VERSION >= 7
|
|
827
|
+
bool IsDetached() const;
|
|
828
|
+
void Detach();
|
|
829
|
+
#endif // NAPI_VERSION >= 7
|
|
825
830
|
};
|
|
826
831
|
|
|
827
832
|
/// A JavaScript typed-array value with unknown array type.
|
|
@@ -2244,6 +2249,196 @@ namespace Napi {
|
|
|
2244
2249
|
napi_threadsafe_function _tsfn;
|
|
2245
2250
|
};
|
|
2246
2251
|
|
|
2252
|
+
// A TypedThreadSafeFunction by default has no context (nullptr) and can
|
|
2253
|
+
// accept any type (void) to its CallJs.
|
|
2254
|
+
template <typename ContextType = std::nullptr_t,
|
|
2255
|
+
typename DataType = void,
|
|
2256
|
+
void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*) =
|
|
2257
|
+
nullptr>
|
|
2258
|
+
class TypedThreadSafeFunction {
|
|
2259
|
+
public:
|
|
2260
|
+
// This API may only be called from the main thread.
|
|
2261
|
+
// Helper function that returns nullptr if running N-API 5+, otherwise a
|
|
2262
|
+
// non-empty, no-op Function. This provides the ability to specify at
|
|
2263
|
+
// compile-time a callback parameter to `New` that safely does no action
|
|
2264
|
+
// when targeting _any_ N-API version.
|
|
2265
|
+
#if NAPI_VERSION > 4
|
|
2266
|
+
static std::nullptr_t EmptyFunctionFactory(Napi::Env env);
|
|
2267
|
+
#else
|
|
2268
|
+
static Napi::Function EmptyFunctionFactory(Napi::Env env);
|
|
2269
|
+
#endif
|
|
2270
|
+
static Napi::Function FunctionOrEmpty(Napi::Env env,
|
|
2271
|
+
Napi::Function& callback);
|
|
2272
|
+
|
|
2273
|
+
#if NAPI_VERSION > 4
|
|
2274
|
+
// This API may only be called from the main thread.
|
|
2275
|
+
// Creates a new threadsafe function with:
|
|
2276
|
+
// Callback [missing] Resource [missing] Finalizer [missing]
|
|
2277
|
+
template <typename ResourceString>
|
|
2278
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2279
|
+
napi_env env,
|
|
2280
|
+
ResourceString resourceName,
|
|
2281
|
+
size_t maxQueueSize,
|
|
2282
|
+
size_t initialThreadCount,
|
|
2283
|
+
ContextType* context = nullptr);
|
|
2284
|
+
|
|
2285
|
+
// This API may only be called from the main thread.
|
|
2286
|
+
// Creates a new threadsafe function with:
|
|
2287
|
+
// Callback [missing] Resource [passed] Finalizer [missing]
|
|
2288
|
+
template <typename ResourceString>
|
|
2289
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2290
|
+
napi_env env,
|
|
2291
|
+
const Object& resource,
|
|
2292
|
+
ResourceString resourceName,
|
|
2293
|
+
size_t maxQueueSize,
|
|
2294
|
+
size_t initialThreadCount,
|
|
2295
|
+
ContextType* context = nullptr);
|
|
2296
|
+
|
|
2297
|
+
// This API may only be called from the main thread.
|
|
2298
|
+
// Creates a new threadsafe function with:
|
|
2299
|
+
// Callback [missing] Resource [missing] Finalizer [passed]
|
|
2300
|
+
template <typename ResourceString,
|
|
2301
|
+
typename Finalizer,
|
|
2302
|
+
typename FinalizerDataType = void>
|
|
2303
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2304
|
+
napi_env env,
|
|
2305
|
+
ResourceString resourceName,
|
|
2306
|
+
size_t maxQueueSize,
|
|
2307
|
+
size_t initialThreadCount,
|
|
2308
|
+
ContextType* context,
|
|
2309
|
+
Finalizer finalizeCallback,
|
|
2310
|
+
FinalizerDataType* data = nullptr);
|
|
2311
|
+
|
|
2312
|
+
// This API may only be called from the main thread.
|
|
2313
|
+
// Creates a new threadsafe function with:
|
|
2314
|
+
// Callback [missing] Resource [passed] Finalizer [passed]
|
|
2315
|
+
template <typename ResourceString,
|
|
2316
|
+
typename Finalizer,
|
|
2317
|
+
typename FinalizerDataType = void>
|
|
2318
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2319
|
+
napi_env env,
|
|
2320
|
+
const Object& resource,
|
|
2321
|
+
ResourceString resourceName,
|
|
2322
|
+
size_t maxQueueSize,
|
|
2323
|
+
size_t initialThreadCount,
|
|
2324
|
+
ContextType* context,
|
|
2325
|
+
Finalizer finalizeCallback,
|
|
2326
|
+
FinalizerDataType* data = nullptr);
|
|
2327
|
+
#endif
|
|
2328
|
+
|
|
2329
|
+
// This API may only be called from the main thread.
|
|
2330
|
+
// Creates a new threadsafe function with:
|
|
2331
|
+
// Callback [passed] Resource [missing] Finalizer [missing]
|
|
2332
|
+
template <typename ResourceString>
|
|
2333
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2334
|
+
napi_env env,
|
|
2335
|
+
const Function& callback,
|
|
2336
|
+
ResourceString resourceName,
|
|
2337
|
+
size_t maxQueueSize,
|
|
2338
|
+
size_t initialThreadCount,
|
|
2339
|
+
ContextType* context = nullptr);
|
|
2340
|
+
|
|
2341
|
+
// This API may only be called from the main thread.
|
|
2342
|
+
// Creates a new threadsafe function with:
|
|
2343
|
+
// Callback [passed] Resource [passed] Finalizer [missing]
|
|
2344
|
+
template <typename ResourceString>
|
|
2345
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2346
|
+
napi_env env,
|
|
2347
|
+
const Function& callback,
|
|
2348
|
+
const Object& resource,
|
|
2349
|
+
ResourceString resourceName,
|
|
2350
|
+
size_t maxQueueSize,
|
|
2351
|
+
size_t initialThreadCount,
|
|
2352
|
+
ContextType* context = nullptr);
|
|
2353
|
+
|
|
2354
|
+
// This API may only be called from the main thread.
|
|
2355
|
+
// Creates a new threadsafe function with:
|
|
2356
|
+
// Callback [passed] Resource [missing] Finalizer [passed]
|
|
2357
|
+
template <typename ResourceString,
|
|
2358
|
+
typename Finalizer,
|
|
2359
|
+
typename FinalizerDataType = void>
|
|
2360
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2361
|
+
napi_env env,
|
|
2362
|
+
const Function& callback,
|
|
2363
|
+
ResourceString resourceName,
|
|
2364
|
+
size_t maxQueueSize,
|
|
2365
|
+
size_t initialThreadCount,
|
|
2366
|
+
ContextType* context,
|
|
2367
|
+
Finalizer finalizeCallback,
|
|
2368
|
+
FinalizerDataType* data = nullptr);
|
|
2369
|
+
|
|
2370
|
+
// This API may only be called from the main thread.
|
|
2371
|
+
// Creates a new threadsafe function with:
|
|
2372
|
+
// Callback [passed] Resource [passed] Finalizer [passed]
|
|
2373
|
+
template <typename CallbackType,
|
|
2374
|
+
typename ResourceString,
|
|
2375
|
+
typename Finalizer,
|
|
2376
|
+
typename FinalizerDataType>
|
|
2377
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2378
|
+
napi_env env,
|
|
2379
|
+
CallbackType callback,
|
|
2380
|
+
const Object& resource,
|
|
2381
|
+
ResourceString resourceName,
|
|
2382
|
+
size_t maxQueueSize,
|
|
2383
|
+
size_t initialThreadCount,
|
|
2384
|
+
ContextType* context,
|
|
2385
|
+
Finalizer finalizeCallback,
|
|
2386
|
+
FinalizerDataType* data = nullptr);
|
|
2387
|
+
|
|
2388
|
+
TypedThreadSafeFunction<ContextType, DataType, CallJs>();
|
|
2389
|
+
TypedThreadSafeFunction<ContextType, DataType, CallJs>(
|
|
2390
|
+
napi_threadsafe_function tsFunctionValue);
|
|
2391
|
+
|
|
2392
|
+
operator napi_threadsafe_function() const;
|
|
2393
|
+
|
|
2394
|
+
// This API may be called from any thread.
|
|
2395
|
+
napi_status BlockingCall(DataType* data = nullptr) const;
|
|
2396
|
+
|
|
2397
|
+
// This API may be called from any thread.
|
|
2398
|
+
napi_status NonBlockingCall(DataType* data = nullptr) const;
|
|
2399
|
+
|
|
2400
|
+
// This API may only be called from the main thread.
|
|
2401
|
+
void Ref(napi_env env) const;
|
|
2402
|
+
|
|
2403
|
+
// This API may only be called from the main thread.
|
|
2404
|
+
void Unref(napi_env env) const;
|
|
2405
|
+
|
|
2406
|
+
// This API may be called from any thread.
|
|
2407
|
+
napi_status Acquire() const;
|
|
2408
|
+
|
|
2409
|
+
// This API may be called from any thread.
|
|
2410
|
+
napi_status Release();
|
|
2411
|
+
|
|
2412
|
+
// This API may be called from any thread.
|
|
2413
|
+
napi_status Abort();
|
|
2414
|
+
|
|
2415
|
+
// This API may be called from any thread.
|
|
2416
|
+
ContextType* GetContext() const;
|
|
2417
|
+
|
|
2418
|
+
private:
|
|
2419
|
+
template <typename ResourceString,
|
|
2420
|
+
typename Finalizer,
|
|
2421
|
+
typename FinalizerDataType>
|
|
2422
|
+
static TypedThreadSafeFunction<ContextType, DataType, CallJs> New(
|
|
2423
|
+
napi_env env,
|
|
2424
|
+
const Function& callback,
|
|
2425
|
+
const Object& resource,
|
|
2426
|
+
ResourceString resourceName,
|
|
2427
|
+
size_t maxQueueSize,
|
|
2428
|
+
size_t initialThreadCount,
|
|
2429
|
+
ContextType* context,
|
|
2430
|
+
Finalizer finalizeCallback,
|
|
2431
|
+
FinalizerDataType* data,
|
|
2432
|
+
napi_finalize wrapper);
|
|
2433
|
+
|
|
2434
|
+
static void CallJsInternal(napi_env env,
|
|
2435
|
+
napi_value jsCallback,
|
|
2436
|
+
void* context,
|
|
2437
|
+
void* data);
|
|
2438
|
+
|
|
2439
|
+
protected:
|
|
2440
|
+
napi_threadsafe_function _tsfn;
|
|
2441
|
+
};
|
|
2247
2442
|
template <typename DataType>
|
|
2248
2443
|
class AsyncProgressWorkerBase : public AsyncWorker {
|
|
2249
2444
|
public:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"versions": [
|
|
3
|
+
{
|
|
4
|
+
"version": "*",
|
|
5
|
+
"target": {
|
|
6
|
+
"node": "active"
|
|
7
|
+
},
|
|
8
|
+
"response": {
|
|
9
|
+
"type": "time-permitting",
|
|
10
|
+
"paid": false,
|
|
11
|
+
"contact": {
|
|
12
|
+
"name": "node-addon-api team",
|
|
13
|
+
"url": "https://github.com/nodejs/node-addon-api/issues"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"backing": [ { "project": "https://github.com/nodejs" },
|
|
17
|
+
{ "foundation": "https://openjsf.org/" }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
"name": "Bill Gallafent",
|
|
52
52
|
"url": "https://github.com/gallafent"
|
|
53
53
|
},
|
|
54
|
+
{
|
|
55
|
+
"name": "blagoev",
|
|
56
|
+
"url": "https://github.com/blagoev"
|
|
57
|
+
},
|
|
54
58
|
{
|
|
55
59
|
"name": "Bruce A. MacNaughton",
|
|
56
60
|
"url": "https://github.com/bmacnaughton"
|
|
@@ -63,6 +67,10 @@
|
|
|
63
67
|
"name": "Daniel Bevenius",
|
|
64
68
|
"url": "https://github.com/danbev"
|
|
65
69
|
},
|
|
70
|
+
{
|
|
71
|
+
"name": "Darshan Sen",
|
|
72
|
+
"url": "https://github.com/RaisinTen"
|
|
73
|
+
},
|
|
66
74
|
{
|
|
67
75
|
"name": "David Halls",
|
|
68
76
|
"url": "https://github.com/davedoesdev"
|
|
@@ -75,6 +83,10 @@
|
|
|
75
83
|
"name": "Dongjin Na",
|
|
76
84
|
"url": "https://github.com/nadongguri"
|
|
77
85
|
},
|
|
86
|
+
{
|
|
87
|
+
"name": "Ferdinand Holzer",
|
|
88
|
+
"url": "https://github.com/fholzer"
|
|
89
|
+
},
|
|
78
90
|
{
|
|
79
91
|
"name": "Eric Bickle",
|
|
80
92
|
"url": "https://github.com/ebickle"
|
|
@@ -91,6 +103,10 @@
|
|
|
91
103
|
"name": "Gus Caplan",
|
|
92
104
|
"url": "https://github.com/devsnek"
|
|
93
105
|
},
|
|
106
|
+
{
|
|
107
|
+
"name": "Helio Frota",
|
|
108
|
+
"url": "https://github.com/helio-frota"
|
|
109
|
+
},
|
|
94
110
|
{
|
|
95
111
|
"name": "Hitesh Kanwathirtha",
|
|
96
112
|
"url": "https://github.com/digitalinfinity"
|
|
@@ -139,6 +155,10 @@
|
|
|
139
155
|
"name": "Kevin Eady",
|
|
140
156
|
"url": "https://github.com/KevinEady"
|
|
141
157
|
},
|
|
158
|
+
{
|
|
159
|
+
"name": "kidneysolo",
|
|
160
|
+
"url": "https://github.com/kidneysolo"
|
|
161
|
+
},
|
|
142
162
|
{
|
|
143
163
|
"name": "Koki Nishihara",
|
|
144
164
|
"url": "https://github.com/Nishikoh"
|
|
@@ -163,6 +183,10 @@
|
|
|
163
183
|
"name": "Luciano Martorella",
|
|
164
184
|
"url": "https://github.com/lmartorella"
|
|
165
185
|
},
|
|
186
|
+
{
|
|
187
|
+
"name": "mastergberry",
|
|
188
|
+
"url": "https://github.com/mastergberry"
|
|
189
|
+
},
|
|
166
190
|
{
|
|
167
191
|
"name": "Mathias Küsel",
|
|
168
192
|
"url": "https://github.com/mathiask88"
|
|
@@ -187,6 +211,10 @@
|
|
|
187
211
|
"name": "Mikhail Cheshkov",
|
|
188
212
|
"url": "https://github.com/mcheshkov"
|
|
189
213
|
},
|
|
214
|
+
{
|
|
215
|
+
"name": "nempoBu4",
|
|
216
|
+
"url": "https://github.com/nempoBu4"
|
|
217
|
+
},
|
|
190
218
|
{
|
|
191
219
|
"name": "Nicola Del Gobbo",
|
|
192
220
|
"url": "https://github.com/NickNaso"
|
|
@@ -195,6 +223,10 @@
|
|
|
195
223
|
"name": "Nick Soggin",
|
|
196
224
|
"url": "https://github.com/iSkore"
|
|
197
225
|
},
|
|
226
|
+
{
|
|
227
|
+
"name": "Nikolai Vavilov",
|
|
228
|
+
"url": "https://github.com/seishun"
|
|
229
|
+
},
|
|
198
230
|
{
|
|
199
231
|
"name": "Nurbol Alpysbayev",
|
|
200
232
|
"url": "https://github.com/anurbol"
|
|
@@ -258,14 +290,20 @@
|
|
|
258
290
|
{
|
|
259
291
|
"name": "Yulong Wang",
|
|
260
292
|
"url": "https://github.com/fs-eire"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"name": "Ziqiu Zhao",
|
|
296
|
+
"url": "https://github.com/ZzqiZQute"
|
|
261
297
|
}
|
|
262
298
|
],
|
|
263
299
|
"dependencies": {},
|
|
264
300
|
"description": "Node.js API (N-API)",
|
|
265
301
|
"devDependencies": {
|
|
266
302
|
"benchmark": "^2.1.4",
|
|
267
|
-
"fs-extra": "^9.0.1",
|
|
268
303
|
"bindings": "^1.5.0",
|
|
304
|
+
"clang-format": "^1.4.0",
|
|
305
|
+
"fs-extra": "^9.0.1",
|
|
306
|
+
"pre-commit": "^1.2.2",
|
|
269
307
|
"safe-buffer": "^5.1.1"
|
|
270
308
|
},
|
|
271
309
|
"directories": {},
|
|
@@ -300,7 +338,11 @@
|
|
|
300
338
|
"dev": "node test",
|
|
301
339
|
"predev:incremental": "node-gyp configure build -C test --debug",
|
|
302
340
|
"dev:incremental": "node test",
|
|
303
|
-
"doc": "doxygen doc/Doxyfile"
|
|
341
|
+
"doc": "doxygen doc/Doxyfile",
|
|
342
|
+
"lint": "node tools/clang-format.js",
|
|
343
|
+
"lint:fix": "git-clang-format '*.h', '*.cc'"
|
|
304
344
|
},
|
|
305
|
-
"
|
|
345
|
+
"pre-commit": "lint",
|
|
346
|
+
"version": "3.1.0",
|
|
347
|
+
"support": true
|
|
306
348
|
}
|
package/tools/README.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Tools
|
|
2
|
+
|
|
3
|
+
## clang-format
|
|
4
|
+
|
|
5
|
+
The clang-format checking tools is designed to check changed lines of code compared to given git-refs.
|
|
6
|
+
|
|
7
|
+
## Migration Script
|
|
2
8
|
|
|
3
9
|
The migration tool is designed to reduce repetitive work in the migration process. However, the script is not aiming to convert every thing for you. There are usually some small fixes and major reconstruction required.
|
|
4
10
|
|
|
@@ -42,7 +48,7 @@ and define it as
|
|
|
42
48
|
This way, the `Napi::ObjectWrap` constructor will be invoked after the object has been instantiated and `Napi::ObjectWrap` can use the `this` pointer to create a reference to the wrapped object.
|
|
43
49
|
|
|
44
50
|
2. Move your original constructor code into the new constructor. Delete your original constructor.
|
|
45
|
-
3. In your class initialization function, associate native methods in the following way.
|
|
51
|
+
3. In your class initialization function, associate native methods in the following way.
|
|
46
52
|
```
|
|
47
53
|
Napi::FunctionReference constructor;
|
|
48
54
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const spawn = require('child_process').spawnSync;
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const filesToCheck = ['*.h', '*.cc'];
|
|
7
|
+
const CLANG_FORMAT_START = process.env.CLANG_FORMAT_START || 'master';
|
|
8
|
+
|
|
9
|
+
function main(args) {
|
|
10
|
+
let clangFormatPath = path.dirname(require.resolve('clang-format'));
|
|
11
|
+
const options = ['--binary=node_modules/.bin/clang-format', '--style=file'];
|
|
12
|
+
|
|
13
|
+
const gitClangFormatPath = path.join(clangFormatPath,
|
|
14
|
+
'bin/git-clang-format');
|
|
15
|
+
const result = spawn('python', [
|
|
16
|
+
gitClangFormatPath,
|
|
17
|
+
...options,
|
|
18
|
+
'--diff',
|
|
19
|
+
CLANG_FORMAT_START,
|
|
20
|
+
'HEAD',
|
|
21
|
+
...filesToCheck
|
|
22
|
+
], { encoding: 'utf-8' });
|
|
23
|
+
|
|
24
|
+
if (result.error) {
|
|
25
|
+
console.error('Error running git-clang-format:', result.error);
|
|
26
|
+
return 2;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const clangFormatOutput = result.stdout.trim();
|
|
30
|
+
if (clangFormatOutput !== '' &&
|
|
31
|
+
clangFormatOutput !== ('no modified files to format') &&
|
|
32
|
+
clangFormatOutput !== ('clang-format did not modify any files')) {
|
|
33
|
+
console.error(clangFormatOutput);
|
|
34
|
+
const fixCmd = '"npm run lint:fix"';
|
|
35
|
+
console.error(`
|
|
36
|
+
ERROR: please run ${fixCmd} to format changes in your commit
|
|
37
|
+
Note that when running the command locally, please keep your local
|
|
38
|
+
master branch and working branch up to date with nodejs/node-addon-api
|
|
39
|
+
to exclude un-related complains.
|
|
40
|
+
Or you can run "env CLANG_FORMAT_START=upstream/master ${fixCmd}".`);
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (require.main === module) {
|
|
46
|
+
process.exitCode = main(process.argv.slice(2));
|
|
47
|
+
}
|