react-native-nitro-storage 0.8.0 → 0.10.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/CHANGELOG.md +107 -25
- package/README.md +140 -27
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +5 -2
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +240 -34
- package/cpp/bindings/HybridStorage.cpp +109 -293
- package/cpp/bindings/HybridStorage.hpp +17 -10
- package/docs/api-reference.md +75 -19
- package/docs/benchmarks.md +6 -1
- package/docs/keychain-lifecycle-testing.md +36 -0
- package/docs/recipes.md +1 -2
- package/docs/secure-storage.md +45 -9
- package/ios/IOSStorageAdapterCpp.mm +391 -175
- package/lib/commonjs/capabilities.js +3 -1
- package/lib/commonjs/capabilities.js.map +1 -1
- package/lib/commonjs/core/durability.js +110 -43
- package/lib/commonjs/core/durability.js.map +1 -1
- package/lib/commonjs/index.js +15 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +219 -97
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +3 -11
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/shared.js +62 -2
- package/lib/commonjs/shared.js.map +1 -1
- package/lib/commonjs/storage-core.js +859 -145
- package/lib/commonjs/storage-core.js.map +1 -1
- package/lib/commonjs/storage-runtime.js +5 -1
- package/lib/commonjs/storage-runtime.js.map +1 -1
- package/lib/commonjs/testing.js +13 -0
- package/lib/commonjs/testing.js.map +1 -1
- package/lib/module/capabilities.js +2 -1
- package/lib/module/capabilities.js.map +1 -1
- package/lib/module/core/durability.js +110 -43
- package/lib/module/core/durability.js.map +1 -1
- package/lib/module/index.js +12 -8
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +215 -99
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +2 -9
- package/lib/module/internal.js.map +1 -1
- package/lib/module/shared.js +60 -2
- package/lib/module/shared.js.map +1 -1
- package/lib/module/storage-core.js +860 -146
- package/lib/module/storage-core.js.map +1 -1
- package/lib/module/storage-runtime.js +4 -1
- package/lib/module/storage-runtime.js.map +1 -1
- package/lib/module/testing.js +8 -1
- package/lib/module/testing.js.map +1 -1
- package/lib/typescript/capabilities.d.ts +2 -1
- package/lib/typescript/capabilities.d.ts.map +1 -1
- package/lib/typescript/core/durability.d.ts +7 -2
- package/lib/typescript/core/durability.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +3 -29
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +0 -2
- package/lib/typescript/internal.d.ts.map +1 -1
- package/lib/typescript/shared.d.ts +19 -0
- package/lib/typescript/shared.d.ts.map +1 -1
- package/lib/typescript/storage-core.d.ts +12 -3
- package/lib/typescript/storage-core.d.ts.map +1 -1
- package/lib/typescript/storage-runtime.d.ts +2 -1
- package/lib/typescript/storage-runtime.d.ts.map +1 -1
- package/lib/typescript/testing.d.ts +1 -1
- package/lib/typescript/testing.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +1 -1
- package/package.json +3 -3
- package/react-native-nitro-storage.podspec +3 -1
- package/src/capabilities.ts +3 -1
- package/src/core/durability.ts +139 -39
- package/src/index.ts +20 -10
- package/src/index.web.ts +328 -166
- package/src/internal.ts +0 -14
- package/src/shared.ts +85 -0
- package/src/storage-core.ts +1165 -199
- package/src/storage-runtime.ts +6 -0
- package/src/testing.ts +8 -1
|
@@ -14,8 +14,16 @@
|
|
|
14
14
|
namespace margelo::nitro::NitroStorage {
|
|
15
15
|
|
|
16
16
|
namespace {
|
|
17
|
-
constexpr auto kBatchMissingSentinel = "__nitro_storage_batch_missing__::v1";
|
|
18
17
|
constexpr int kDefaultBiometricLevel = 2;
|
|
18
|
+
|
|
19
|
+
template <typename Map>
|
|
20
|
+
size_t mapMemorySize(const Map& map) noexcept {
|
|
21
|
+
size_t total = map.size() * sizeof(typename Map::value_type);
|
|
22
|
+
if constexpr (requires { map.bucket_count(); }) {
|
|
23
|
+
total += map.bucket_count() * sizeof(void*);
|
|
24
|
+
}
|
|
25
|
+
return total;
|
|
26
|
+
}
|
|
19
27
|
} // namespace
|
|
20
28
|
|
|
21
29
|
HybridStorage::HybridStorage()
|
|
@@ -57,27 +65,16 @@ void HybridStorage::set(const std::string& key, const std::string& value, double
|
|
|
57
65
|
}
|
|
58
66
|
case Scope::Disk:
|
|
59
67
|
ensureAdapter();
|
|
60
|
-
|
|
61
|
-
nativeAdapter_->setDisk(key, value);
|
|
62
|
-
} catch (const std::exception&) {
|
|
63
|
-
throw;
|
|
64
|
-
} catch (...) {
|
|
65
|
-
throw std::runtime_error("NitroStorage: Disk set failed (unknown error)");
|
|
66
|
-
}
|
|
68
|
+
runAdapterOperation(
|
|
69
|
+
[&] { nativeAdapter_->setDisk(key, value); }, "Disk set");
|
|
67
70
|
break;
|
|
68
71
|
case Scope::Secure:
|
|
69
72
|
ensureAdapter();
|
|
70
|
-
|
|
71
|
-
nativeAdapter_->setSecure(key, value);
|
|
72
|
-
} catch (const std::exception&) {
|
|
73
|
-
throw;
|
|
74
|
-
} catch (...) {
|
|
75
|
-
throw std::runtime_error("NitroStorage: Secure set failed (unknown error)");
|
|
76
|
-
}
|
|
73
|
+
runAdapterOperation(
|
|
74
|
+
[&] { nativeAdapter_->setSecure(key, value); }, "Secure set");
|
|
77
75
|
break;
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
onKeySet(static_cast<int>(s), key);
|
|
81
78
|
notifyListeners(static_cast<int>(s), key, value);
|
|
82
79
|
}
|
|
83
80
|
|
|
@@ -95,22 +92,12 @@ std::optional<std::string> HybridStorage::get(const std::string& key, double sco
|
|
|
95
92
|
}
|
|
96
93
|
case Scope::Disk:
|
|
97
94
|
ensureAdapter();
|
|
98
|
-
|
|
99
|
-
return nativeAdapter_->getDisk(key);
|
|
100
|
-
} catch (const std::exception&) {
|
|
101
|
-
throw;
|
|
102
|
-
} catch (...) {
|
|
103
|
-
throw std::runtime_error("NitroStorage: Disk get failed (unknown error)");
|
|
104
|
-
}
|
|
95
|
+
return runAdapterOperation(
|
|
96
|
+
[&] { return nativeAdapter_->getDisk(key); }, "Disk get");
|
|
105
97
|
case Scope::Secure:
|
|
106
98
|
ensureAdapter();
|
|
107
|
-
|
|
108
|
-
return nativeAdapter_->getSecure(key);
|
|
109
|
-
} catch (const std::exception&) {
|
|
110
|
-
throw;
|
|
111
|
-
} catch (...) {
|
|
112
|
-
throw std::runtime_error("NitroStorage: Secure get failed (unknown error)");
|
|
113
|
-
}
|
|
99
|
+
return runAdapterOperation(
|
|
100
|
+
[&] { return nativeAdapter_->getSecure(key); }, "Secure get");
|
|
114
101
|
}
|
|
115
102
|
|
|
116
103
|
return std::nullopt;
|
|
@@ -127,27 +114,16 @@ void HybridStorage::remove(const std::string& key, double scope) {
|
|
|
127
114
|
}
|
|
128
115
|
case Scope::Disk:
|
|
129
116
|
ensureAdapter();
|
|
130
|
-
|
|
131
|
-
nativeAdapter_->deleteDisk(key);
|
|
132
|
-
} catch (const std::exception&) {
|
|
133
|
-
throw;
|
|
134
|
-
} catch (...) {
|
|
135
|
-
throw std::runtime_error("NitroStorage: Disk delete failed (unknown error)");
|
|
136
|
-
}
|
|
117
|
+
runAdapterOperation(
|
|
118
|
+
[&] { nativeAdapter_->deleteDisk(key); }, "Disk delete");
|
|
137
119
|
break;
|
|
138
120
|
case Scope::Secure:
|
|
139
121
|
ensureAdapter();
|
|
140
|
-
|
|
141
|
-
nativeAdapter_->deleteSecure(key);
|
|
142
|
-
} catch (const std::exception&) {
|
|
143
|
-
throw;
|
|
144
|
-
} catch (...) {
|
|
145
|
-
throw std::runtime_error("NitroStorage: Secure delete failed (unknown error)");
|
|
146
|
-
}
|
|
122
|
+
runAdapterOperation(
|
|
123
|
+
[&] { nativeAdapter_->deleteSecure(key); }, "Secure delete");
|
|
147
124
|
break;
|
|
148
125
|
}
|
|
149
126
|
|
|
150
|
-
onKeyRemove(static_cast<int>(s), key);
|
|
151
127
|
notifyListeners(static_cast<int>(s), key, std::nullopt);
|
|
152
128
|
}
|
|
153
129
|
|
|
@@ -160,16 +136,13 @@ bool HybridStorage::has(const std::string& key, double scope) {
|
|
|
160
136
|
return memoryStore_.find(key) != memoryStore_.end();
|
|
161
137
|
}
|
|
162
138
|
case Scope::Disk:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
return indexIt->second.count(key) > 0;
|
|
172
|
-
}
|
|
139
|
+
ensureAdapter();
|
|
140
|
+
return runAdapterOperation(
|
|
141
|
+
[&] { return nativeAdapter_->hasDisk(key); }, "Disk has");
|
|
142
|
+
case Scope::Secure:
|
|
143
|
+
ensureAdapter();
|
|
144
|
+
return runAdapterOperation(
|
|
145
|
+
[&] { return nativeAdapter_->hasSecure(key); }, "Secure has");
|
|
173
146
|
}
|
|
174
147
|
return false;
|
|
175
148
|
}
|
|
@@ -188,16 +161,13 @@ std::vector<std::string> HybridStorage::getAllKeys(double scope) {
|
|
|
188
161
|
return keys;
|
|
189
162
|
}
|
|
190
163
|
case Scope::Disk:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
return toVector(indexIt->second);
|
|
200
|
-
}
|
|
164
|
+
ensureAdapter();
|
|
165
|
+
return runAdapterOperation(
|
|
166
|
+
[&] { return nativeAdapter_->getAllKeysDisk(); }, "Disk getAllKeys");
|
|
167
|
+
case Scope::Secure:
|
|
168
|
+
ensureAdapter();
|
|
169
|
+
return runAdapterOperation(
|
|
170
|
+
[&] { return nativeAdapter_->getAllKeysSecure(); }, "Secure getAllKeys");
|
|
201
171
|
}
|
|
202
172
|
return {};
|
|
203
173
|
}
|
|
@@ -221,23 +191,13 @@ std::vector<std::string> HybridStorage::getKeysByPrefix(const std::string& prefi
|
|
|
221
191
|
return keys;
|
|
222
192
|
}
|
|
223
193
|
case Scope::Disk:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return keys;
|
|
232
|
-
}
|
|
233
|
-
keys.reserve(indexIt->second.size());
|
|
234
|
-
for (const auto& key : indexIt->second) {
|
|
235
|
-
if (key.rfind(prefix, 0) == 0) {
|
|
236
|
-
keys.push_back(key);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return keys;
|
|
240
|
-
}
|
|
194
|
+
ensureAdapter();
|
|
195
|
+
return runAdapterOperation(
|
|
196
|
+
[&] { return nativeAdapter_->getKeysByPrefixDisk(prefix); }, "Disk getKeysByPrefix");
|
|
197
|
+
case Scope::Secure:
|
|
198
|
+
ensureAdapter();
|
|
199
|
+
return runAdapterOperation(
|
|
200
|
+
[&] { return nativeAdapter_->getKeysByPrefixSecure(prefix); }, "Secure getKeysByPrefix");
|
|
241
201
|
}
|
|
242
202
|
return {};
|
|
243
203
|
}
|
|
@@ -251,20 +211,46 @@ double HybridStorage::size(double scope) {
|
|
|
251
211
|
return static_cast<double>(memoryStore_.size());
|
|
252
212
|
}
|
|
253
213
|
case Scope::Disk:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
return static_cast<double>(indexIt->second.size());
|
|
263
|
-
}
|
|
214
|
+
ensureAdapter();
|
|
215
|
+
return static_cast<double>(runAdapterOperation(
|
|
216
|
+
[&] { return nativeAdapter_->sizeDisk(); }, "Disk size"));
|
|
217
|
+
case Scope::Secure:
|
|
218
|
+
ensureAdapter();
|
|
219
|
+
return static_cast<double>(runAdapterOperation(
|
|
220
|
+
[&] { return nativeAdapter_->sizeSecure(); }, "Secure size"));
|
|
264
221
|
}
|
|
265
222
|
return 0.0;
|
|
266
223
|
}
|
|
267
224
|
|
|
225
|
+
size_t HybridStorage::getExternalMemorySize() noexcept {
|
|
226
|
+
return memorySize();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
size_t HybridStorage::memorySize() noexcept {
|
|
230
|
+
size_t total = 0;
|
|
231
|
+
{
|
|
232
|
+
std::lock_guard<std::mutex> lock(memoryMutex_);
|
|
233
|
+
total += mapMemorySize(memoryStore_);
|
|
234
|
+
for (const auto& [key, value] : memoryStore_) {
|
|
235
|
+
total += key.capacity() * sizeof(std::string::value_type);
|
|
236
|
+
total += value.capacity() * sizeof(std::string::value_type);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
{
|
|
240
|
+
std::lock_guard<std::mutex> lock(listenersMutex_);
|
|
241
|
+
total += mapMemorySize(listeners_);
|
|
242
|
+
for (const auto& [scope, listeners] : listeners_) {
|
|
243
|
+
(void)scope;
|
|
244
|
+
total += listeners.capacity() * sizeof(Listener);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// The adapter interface exposes no memory-sizing contract, so platform
|
|
248
|
+
// preference/keychain caches are intentionally outside this bounded
|
|
249
|
+
// HybridStorage-owned estimate. std::function target allocations are also
|
|
250
|
+
// opaque; the retained Listener wrapper itself is counted above.
|
|
251
|
+
return total;
|
|
252
|
+
}
|
|
253
|
+
|
|
268
254
|
std::function<void()> HybridStorage::addOnChange(
|
|
269
255
|
double scope,
|
|
270
256
|
const std::function<void(const std::string&, const std::optional<std::string>&)>& callback
|
|
@@ -317,27 +303,16 @@ void HybridStorage::clear(double scope) {
|
|
|
317
303
|
}
|
|
318
304
|
case Scope::Disk:
|
|
319
305
|
ensureAdapter();
|
|
320
|
-
|
|
321
|
-
nativeAdapter_->clearDisk();
|
|
322
|
-
} catch (const std::exception&) {
|
|
323
|
-
throw;
|
|
324
|
-
} catch (...) {
|
|
325
|
-
throw std::runtime_error("NitroStorage: Disk clear failed (unknown error)");
|
|
326
|
-
}
|
|
306
|
+
runAdapterOperation(
|
|
307
|
+
[&] { nativeAdapter_->clearDisk(); }, "Disk clear");
|
|
327
308
|
break;
|
|
328
309
|
case Scope::Secure:
|
|
329
310
|
ensureAdapter();
|
|
330
|
-
|
|
331
|
-
nativeAdapter_->clearSecure();
|
|
332
|
-
} catch (const std::exception&) {
|
|
333
|
-
throw;
|
|
334
|
-
} catch (...) {
|
|
335
|
-
throw std::runtime_error("NitroStorage: Secure clear failed (unknown error)");
|
|
336
|
-
}
|
|
311
|
+
runAdapterOperation(
|
|
312
|
+
[&] { nativeAdapter_->clearSecure(); }, "Secure clear");
|
|
337
313
|
break;
|
|
338
314
|
}
|
|
339
315
|
|
|
340
|
-
onScopeClear(static_cast<int>(s));
|
|
341
316
|
notifyListeners(static_cast<int>(s), kClearSentinelKey, std::nullopt);
|
|
342
317
|
}
|
|
343
318
|
|
|
@@ -358,38 +333,25 @@ void HybridStorage::setBatch(const std::vector<std::string>& keys, const std::ve
|
|
|
358
333
|
}
|
|
359
334
|
case Scope::Disk:
|
|
360
335
|
ensureAdapter();
|
|
361
|
-
|
|
362
|
-
nativeAdapter_->setDiskBatch(keys, values);
|
|
363
|
-
} catch (const std::exception&) {
|
|
364
|
-
throw;
|
|
365
|
-
} catch (...) {
|
|
366
|
-
throw std::runtime_error("NitroStorage: Disk setBatch failed (unknown error)");
|
|
367
|
-
}
|
|
336
|
+
runAdapterOperation(
|
|
337
|
+
[&] { nativeAdapter_->setDiskBatch(keys, values); }, "Disk setBatch");
|
|
368
338
|
break;
|
|
369
339
|
case Scope::Secure:
|
|
370
340
|
ensureAdapter();
|
|
371
|
-
|
|
372
|
-
nativeAdapter_->setSecureBatch(keys, values);
|
|
373
|
-
} catch (const std::exception&) {
|
|
374
|
-
throw;
|
|
375
|
-
} catch (...) {
|
|
376
|
-
throw std::runtime_error("NitroStorage: Secure setBatch failed (unknown error)");
|
|
377
|
-
}
|
|
341
|
+
runAdapterOperation(
|
|
342
|
+
[&] { nativeAdapter_->setSecureBatch(keys, values); }, "Secure setBatch");
|
|
378
343
|
break;
|
|
379
344
|
}
|
|
380
345
|
|
|
381
346
|
const auto scopeValue = static_cast<int>(s);
|
|
382
|
-
for (const auto& key : keys) {
|
|
383
|
-
onKeySet(scopeValue, key);
|
|
384
|
-
}
|
|
385
347
|
const auto listeners = copyListenersForScope(scopeValue);
|
|
386
348
|
for (size_t i = 0; i < keys.size(); ++i) {
|
|
387
349
|
notifyListeners(listeners, keys[i], values[i]);
|
|
388
350
|
}
|
|
389
351
|
}
|
|
390
352
|
|
|
391
|
-
std::vector<std::string
|
|
392
|
-
std::vector<std::string
|
|
353
|
+
std::vector<std::optional<std::string>> HybridStorage::getBatch(const std::vector<std::string>& keys, double scope) {
|
|
354
|
+
std::vector<std::optional<std::string>> results;
|
|
393
355
|
results.reserve(keys.size());
|
|
394
356
|
|
|
395
357
|
Scope s = toScope(scope);
|
|
@@ -402,42 +364,20 @@ std::vector<std::string> HybridStorage::getBatch(const std::vector<std::string>&
|
|
|
402
364
|
if (it != memoryStore_.end()) {
|
|
403
365
|
results.push_back(it->second);
|
|
404
366
|
} else {
|
|
405
|
-
results.push_back(
|
|
367
|
+
results.push_back(std::nullopt);
|
|
406
368
|
}
|
|
407
369
|
}
|
|
408
370
|
return results;
|
|
409
371
|
}
|
|
410
372
|
case Scope::Disk: {
|
|
411
373
|
ensureAdapter();
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
values = nativeAdapter_->getDiskBatch(keys);
|
|
415
|
-
} catch (const std::exception&) {
|
|
416
|
-
throw;
|
|
417
|
-
} catch (...) {
|
|
418
|
-
throw std::runtime_error("NitroStorage: Disk getBatch failed (unknown error)");
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
for (const auto& value : values) {
|
|
422
|
-
results.push_back(value.has_value() ? *value : std::string(kBatchMissingSentinel));
|
|
423
|
-
}
|
|
424
|
-
return results;
|
|
374
|
+
return runAdapterOperation(
|
|
375
|
+
[&] { return nativeAdapter_->getDiskBatch(keys); }, "Disk getBatch");
|
|
425
376
|
}
|
|
426
377
|
case Scope::Secure: {
|
|
427
378
|
ensureAdapter();
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
values = nativeAdapter_->getSecureBatch(keys);
|
|
431
|
-
} catch (const std::exception&) {
|
|
432
|
-
throw;
|
|
433
|
-
} catch (...) {
|
|
434
|
-
throw std::runtime_error("NitroStorage: Secure getBatch failed (unknown error)");
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
for (const auto& value : values) {
|
|
438
|
-
results.push_back(value.has_value() ? *value : std::string(kBatchMissingSentinel));
|
|
439
|
-
}
|
|
440
|
-
return results;
|
|
379
|
+
return runAdapterOperation(
|
|
380
|
+
[&] { return nativeAdapter_->getSecureBatch(keys); }, "Secure getBatch");
|
|
441
381
|
}
|
|
442
382
|
}
|
|
443
383
|
|
|
@@ -457,30 +397,17 @@ void HybridStorage::removeBatch(const std::vector<std::string>& keys, double sco
|
|
|
457
397
|
}
|
|
458
398
|
case Scope::Disk:
|
|
459
399
|
ensureAdapter();
|
|
460
|
-
|
|
461
|
-
nativeAdapter_->deleteDiskBatch(keys);
|
|
462
|
-
} catch (const std::exception&) {
|
|
463
|
-
throw;
|
|
464
|
-
} catch (...) {
|
|
465
|
-
throw std::runtime_error("NitroStorage: Disk removeBatch failed (unknown error)");
|
|
466
|
-
}
|
|
400
|
+
runAdapterOperation(
|
|
401
|
+
[&] { nativeAdapter_->deleteDiskBatch(keys); }, "Disk removeBatch");
|
|
467
402
|
break;
|
|
468
403
|
case Scope::Secure:
|
|
469
404
|
ensureAdapter();
|
|
470
|
-
|
|
471
|
-
nativeAdapter_->deleteSecureBatch(keys);
|
|
472
|
-
} catch (const std::exception&) {
|
|
473
|
-
throw;
|
|
474
|
-
} catch (...) {
|
|
475
|
-
throw std::runtime_error("NitroStorage: Secure removeBatch failed (unknown error)");
|
|
476
|
-
}
|
|
405
|
+
runAdapterOperation(
|
|
406
|
+
[&] { nativeAdapter_->deleteSecureBatch(keys); }, "Secure removeBatch");
|
|
477
407
|
break;
|
|
478
408
|
}
|
|
479
409
|
|
|
480
410
|
const auto scopeValue = static_cast<int>(s);
|
|
481
|
-
for (const auto& key : keys) {
|
|
482
|
-
onKeyRemove(scopeValue, key);
|
|
483
|
-
}
|
|
484
411
|
const auto listeners = copyListenersForScope(scopeValue);
|
|
485
412
|
for (const auto& key : keys) {
|
|
486
413
|
notifyListeners(listeners, key, std::nullopt);
|
|
@@ -550,43 +477,23 @@ void HybridStorage::setSecureBiometricWithLevel(const std::string& key, const st
|
|
|
550
477
|
"NitroStorage: Invalid biometric level");
|
|
551
478
|
}
|
|
552
479
|
ensureAdapter();
|
|
553
|
-
|
|
554
|
-
nativeAdapter_->setSecureBiometricWithLevel(
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
intLevel
|
|
558
|
-
);
|
|
559
|
-
onKeySet(static_cast<int>(Scope::Secure), key);
|
|
560
|
-
notifyListeners(static_cast<int>(Scope::Secure), key, value);
|
|
561
|
-
} catch (const std::exception&) {
|
|
562
|
-
throw;
|
|
563
|
-
} catch (...) {
|
|
564
|
-
throw std::runtime_error("NitroStorage: Biometric set failed (unknown error)");
|
|
565
|
-
}
|
|
480
|
+
runAdapterOperation(
|
|
481
|
+
[&] { nativeAdapter_->setSecureBiometricWithLevel(key, value, intLevel); },
|
|
482
|
+
"Biometric set");
|
|
483
|
+
notifyListeners(static_cast<int>(Scope::Secure), key, value);
|
|
566
484
|
}
|
|
567
485
|
|
|
568
486
|
std::optional<std::string> HybridStorage::getSecureBiometric(const std::string& key) {
|
|
569
487
|
ensureAdapter();
|
|
570
|
-
|
|
571
|
-
return nativeAdapter_->getSecureBiometric(key);
|
|
572
|
-
} catch (const std::exception&) {
|
|
573
|
-
throw;
|
|
574
|
-
} catch (...) {
|
|
575
|
-
throw std::runtime_error("NitroStorage: Biometric get failed (unknown error)");
|
|
576
|
-
}
|
|
488
|
+
return runAdapterOperation(
|
|
489
|
+
[&] { return nativeAdapter_->getSecureBiometric(key); }, "Biometric get");
|
|
577
490
|
}
|
|
578
491
|
|
|
579
492
|
void HybridStorage::deleteSecureBiometric(const std::string& key) {
|
|
580
493
|
ensureAdapter();
|
|
581
|
-
|
|
582
|
-
nativeAdapter_->deleteSecureBiometric(key);
|
|
583
|
-
|
|
584
|
-
notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
|
|
585
|
-
} catch (const std::exception&) {
|
|
586
|
-
throw;
|
|
587
|
-
} catch (...) {
|
|
588
|
-
throw std::runtime_error("NitroStorage: Biometric delete failed (unknown error)");
|
|
589
|
-
}
|
|
494
|
+
runAdapterOperation(
|
|
495
|
+
[&] { nativeAdapter_->deleteSecureBiometric(key); }, "Biometric delete");
|
|
496
|
+
notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
|
|
590
497
|
}
|
|
591
498
|
|
|
592
499
|
bool HybridStorage::hasSecureBiometric(const std::string& key) {
|
|
@@ -596,22 +503,9 @@ bool HybridStorage::hasSecureBiometric(const std::string& key) {
|
|
|
596
503
|
|
|
597
504
|
void HybridStorage::clearSecureBiometric() {
|
|
598
505
|
ensureAdapter();
|
|
599
|
-
|
|
600
|
-
nativeAdapter_->clearSecureBiometric();
|
|
601
|
-
|
|
602
|
-
// (which will now correctly exclude the cleared biometric keys).
|
|
603
|
-
// We do NOT call onScopeClear() here because that would also clear the index
|
|
604
|
-
// contents for regular secure keys; marking stale is sufficient.
|
|
605
|
-
{
|
|
606
|
-
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
607
|
-
keyIndexHydrated_[static_cast<int>(Scope::Secure)] = false;
|
|
608
|
-
}
|
|
609
|
-
notifyListeners(static_cast<int>(Scope::Secure), kClearSentinelKey, std::nullopt);
|
|
610
|
-
} catch (const std::exception&) {
|
|
611
|
-
throw;
|
|
612
|
-
} catch (...) {
|
|
613
|
-
throw std::runtime_error("NitroStorage: Biometric clear failed (unknown error)");
|
|
614
|
-
}
|
|
506
|
+
runAdapterOperation(
|
|
507
|
+
[&] { nativeAdapter_->clearSecureBiometric(); }, "Biometric clear");
|
|
508
|
+
notifyListeners(static_cast<int>(Scope::Secure), kClearSentinelKey, std::nullopt);
|
|
615
509
|
}
|
|
616
510
|
|
|
617
511
|
// --- Internal ---
|
|
@@ -658,84 +552,6 @@ void HybridStorage::notifyListeners(
|
|
|
658
552
|
notifyListeners(listeners, key, value);
|
|
659
553
|
}
|
|
660
554
|
|
|
661
|
-
std::vector<std::string> HybridStorage::toVector(const std::unordered_set<std::string>& keys) {
|
|
662
|
-
std::vector<std::string> values;
|
|
663
|
-
values.reserve(keys.size());
|
|
664
|
-
for (const auto& key : keys) {
|
|
665
|
-
values.push_back(key);
|
|
666
|
-
}
|
|
667
|
-
return values;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
void HybridStorage::ensureKeyIndexHydrated(int scope) {
|
|
671
|
-
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
676
|
-
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
677
|
-
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
ensureAdapter();
|
|
682
|
-
std::vector<std::string> keys;
|
|
683
|
-
try {
|
|
684
|
-
if (scope == static_cast<int>(Scope::Disk)) {
|
|
685
|
-
keys = nativeAdapter_->getAllKeysDisk();
|
|
686
|
-
} else {
|
|
687
|
-
keys = nativeAdapter_->getAllKeysSecure();
|
|
688
|
-
}
|
|
689
|
-
} catch (const std::exception&) {
|
|
690
|
-
throw;
|
|
691
|
-
} catch (...) {
|
|
692
|
-
throw std::runtime_error("NitroStorage: Key index hydration failed (unknown error)");
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
auto& index = keyIndex_[scope];
|
|
696
|
-
index.clear();
|
|
697
|
-
for (const auto& key : keys) {
|
|
698
|
-
index.insert(key);
|
|
699
|
-
}
|
|
700
|
-
keyIndexHydrated_[scope] = true;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
void HybridStorage::onKeySet(int scope, const std::string& key) {
|
|
704
|
-
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
709
|
-
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
710
|
-
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
711
|
-
keyIndex_[scope].insert(key);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
void HybridStorage::onKeyRemove(int scope, const std::string& key) {
|
|
716
|
-
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
721
|
-
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
722
|
-
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
723
|
-
keyIndex_[scope].erase(key);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
void HybridStorage::onScopeClear(int scope) {
|
|
728
|
-
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
733
|
-
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
734
|
-
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
735
|
-
keyIndex_[scope].clear();
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
555
|
void HybridStorage::ensureAdapter() const {
|
|
740
556
|
if (!nativeAdapter_) {
|
|
741
557
|
throw std::runtime_error("NitroStorage: Native adapter not initialized");
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
#include <functional>
|
|
11
11
|
#include <memory>
|
|
12
12
|
#include <vector>
|
|
13
|
-
#include <unordered_set>
|
|
14
13
|
#include <atomic>
|
|
15
14
|
#include <array>
|
|
15
|
+
#include <stdexcept>
|
|
16
16
|
|
|
17
17
|
namespace margelo::nitro::NitroStorage {
|
|
18
18
|
|
|
@@ -38,8 +38,9 @@ public:
|
|
|
38
38
|
std::vector<std::string> getAllKeys(double scope) override;
|
|
39
39
|
std::vector<std::string> getKeysByPrefix(const std::string& prefix, double scope) override;
|
|
40
40
|
double size(double scope) override;
|
|
41
|
+
size_t getExternalMemorySize() noexcept override;
|
|
41
42
|
void setBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values, double scope) override;
|
|
42
|
-
std::vector<std::string
|
|
43
|
+
std::vector<std::optional<std::string>> getBatch(const std::vector<std::string>& keys, double scope) override;
|
|
43
44
|
void removeBatch(const std::vector<std::string>& keys, double scope) override;
|
|
44
45
|
void removeByPrefix(const std::string& prefix, double scope) override;
|
|
45
46
|
std::function<void()> addOnChange(
|
|
@@ -79,9 +80,6 @@ private:
|
|
|
79
80
|
// Lock-free fast path: skip locking/copying the listener vector on the
|
|
80
81
|
// write/notify path when no listeners are registered for a scope.
|
|
81
82
|
std::array<std::atomic<size_t>, 3> listenerScopeCounts_{};
|
|
82
|
-
HybridStorageMap<int, std::unordered_set<std::string>> keyIndex_;
|
|
83
|
-
HybridStorageMap<int, bool> keyIndexHydrated_;
|
|
84
|
-
std::mutex keyIndexMutex_;
|
|
85
83
|
|
|
86
84
|
std::vector<Listener> copyListenersForScope(int scope);
|
|
87
85
|
void notifyListeners(
|
|
@@ -90,13 +88,22 @@ private:
|
|
|
90
88
|
const std::optional<std::string>& value
|
|
91
89
|
);
|
|
92
90
|
void notifyListeners(int scope, const std::string& key, const std::optional<std::string>& value);
|
|
93
|
-
std::vector<std::string> toVector(const std::unordered_set<std::string>& keys);
|
|
94
|
-
void ensureKeyIndexHydrated(int scope);
|
|
95
|
-
void onKeySet(int scope, const std::string& key);
|
|
96
|
-
void onKeyRemove(int scope, const std::string& key);
|
|
97
|
-
void onScopeClear(int scope);
|
|
98
91
|
void ensureAdapter() const;
|
|
99
92
|
Scope toScope(double scopeValue);
|
|
93
|
+
size_t memorySize() noexcept;
|
|
94
|
+
|
|
95
|
+
template <typename TOperation>
|
|
96
|
+
auto runAdapterOperation(TOperation&& operation, const char* description) -> decltype(operation()) {
|
|
97
|
+
try {
|
|
98
|
+
return operation();
|
|
99
|
+
} catch (const std::exception&) {
|
|
100
|
+
throw;
|
|
101
|
+
} catch (...) {
|
|
102
|
+
throw std::runtime_error(
|
|
103
|
+
std::string("NitroStorage: ") + description + " failed (unknown error)"
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
100
107
|
|
|
101
108
|
static constexpr const char* kClearSentinelKey = "";
|
|
102
109
|
};
|