1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #define LOG_TAG "JsKVManager"
16 #include "js_kv_manager.h"
17 #include "distributed_kv_data_manager.h"
18 #include "js_device_kv_store.h"
19 #include "js_single_kv_store.h"
20 #include "js_util.h"
21 #include "log_print.h"
22 #include "napi_queue.h"
23 #include "js_error_utils.h"
24
25 using namespace OHOS::DistributedKv;
26
27 namespace OHOS::DistributedKVStore {
IsStoreTypeSupported(Options options)28 static bool IsStoreTypeSupported(Options options)
29 {
30 return (options.kvStoreType == KvStoreType::DEVICE_COLLABORATION)
31 || (options.kvStoreType == KvStoreType::SINGLE_VERSION);
32 }
33
JsKVManager(const std::string & bundleName,napi_env env,ContextParam param)34 JsKVManager::JsKVManager(const std::string &bundleName, napi_env env, ContextParam param)
35 : bundleName_(bundleName), uvQueue_(std::make_shared<UvQueue>(env)),
36 param_(std::make_shared<ContextParam>(std::move(param)))
37 {
38 }
39
~JsKVManager()40 JsKVManager::~JsKVManager()
41 {
42 ZLOGD("no memory leak for JsKVManager");
43 std::lock_guard<std::mutex> lck(deathMutex_);
44 for (auto& it : deathRecipient_) {
45 kvDataManager_.UnRegisterKvStoreServiceDeathRecipient(it);
46 it->Clear();
47 }
48 deathRecipient_.clear();
49 }
50
CreateKVManager(napi_env env,napi_callback_info info)51 napi_value JsKVManager::CreateKVManager(napi_env env, napi_callback_info info)
52 {
53 size_t argc = 2;
54 napi_value argv[2] = { nullptr };
55 napi_value result = nullptr;
56 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
57
58 if (argc < 1) {
59 ThrowNapiError(env, Status::INVALID_ARGUMENT, "The number of parameters is incorrect.");
60 return result;
61 }
62
63 std::string bundleName;
64 napi_status status = JSUtil::GetNamedProperty(env, argv[0], "bundleName", bundleName);
65 if (status == napi_generic_failure) {
66 ThrowNapiError(env, Status::INVALID_ARGUMENT, "Missing bundleName parameter.");
67 return result;
68 }
69 if (bundleName.empty()) {
70 ThrowNapiError(env, Status::INVALID_ARGUMENT, "The type of bundleName must be string.");
71 return result;
72 }
73
74 napi_value jsContext = nullptr;
75 status = JSUtil::GetNamedProperty(env, argv[0], "context", jsContext);
76 if (status == napi_generic_failure) {
77 ThrowNapiError(env, Status::INVALID_ARGUMENT, "Missing context parameter.");
78 return result;
79 }
80
81 status = napi_new_instance(env, Constructor(env), argc, argv, &result);
82 if (result == nullptr || status != napi_ok) {
83 ThrowNapiError(env, status, "KVManager::New failed!", false);
84 return result;
85 }
86
87 return result;
88 }
89
90 struct GetKVStoreContext : public ContextBase {
91 std::string storeId;
92 Options options;
93 JsSingleKVStore* kvStore = nullptr;
94 napi_ref ref = nullptr;
95
GetCbInfoOHOS::DistributedKVStore::GetKVStoreContext96 void GetCbInfo(napi_env env, napi_callback_info info)
97 {
98 auto input = [env, this](size_t argc, napi_value* argv) {
99 // required 2 arguments :: <storeId> <options>
100 ASSERT_BUSINESS_ERR(this, argc >= 2, Status::INVALID_ARGUMENT, "The number of parameters is incorrect.");
101 status = JSUtil::GetValue(env, argv[0], storeId);
102 ASSERT_BUSINESS_ERR(this, ((status == napi_ok) && !storeId.empty()), Status::INVALID_ARGUMENT,
103 "The type of storeId must be string.");
104 status = JSUtil::GetValue(env, argv[1], options);
105 ASSERT_BUSINESS_ERR(this, status == napi_ok, Status::INVALID_ARGUMENT, "The type of options is incorrect.");
106 ASSERT_BUSINESS_ERR(this, IsStoreTypeSupported(options), Status::INVALID_ARGUMENT,
107 "The type of kvStoreType is incorrect.");
108 ZLOGD("GetKVStore kvStoreType=%{public}d", options.kvStoreType);
109 if (options.kvStoreType == KvStoreType::DEVICE_COLLABORATION) {
110 ref = JSUtil::NewWithRef(env, argc, argv, reinterpret_cast<void**>(&kvStore),
111 JsDeviceKVStore::Constructor(env));
112 } else if (options.kvStoreType == KvStoreType::SINGLE_VERSION) {
113 ref = JSUtil::NewWithRef(env, argc, argv, reinterpret_cast<void**>(&kvStore),
114 JsSingleKVStore::Constructor(env));
115 }
116 };
117 ContextBase::GetCbInfo(env, info, input);
118 }
119 };
120
121 /*
122 * [JS API Prototype]
123 * [AsyncCallback]
124 * getKVStore<T extends KVStore>(storeId: string, options: Options, callback: AsyncCallback<T>): void;
125 * [Promise]
126 * getKVStore<T extends KVStore>(storeId: string, options: Options): Promise<T>;
127 */
GetKVStore(napi_env env,napi_callback_info info)128 napi_value JsKVManager::GetKVStore(napi_env env, napi_callback_info info)
129 {
130 auto ctxt = std::make_shared<GetKVStoreContext>();
131 ctxt->GetCbInfo(env, info);
132 ASSERT_NULL(!ctxt->isThrowError, "GetKVStore exit");
133
134 auto execute = [ctxt]() {
135 auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
136 ASSERT_ARGS(ctxt, kvm != nullptr, "KVManager is null, failed!");
137 AppId appId = { kvm->bundleName_ };
138 StoreId storeId = { ctxt->storeId };
139 ctxt->options.baseDir = kvm->param_->baseDir;
140 ctxt->options.area = kvm->param_->area + 1;
141 ctxt->options.hapName = kvm->param_->hapName;
142 ZLOGD("Options area:%{public}d dir:%{public}s", ctxt->options.area, ctxt->options.baseDir.c_str());
143 std::shared_ptr<DistributedKv::SingleKvStore> kvStore;
144 Status status = kvm->kvDataManager_.GetSingleKvStore(ctxt->options, appId, storeId, kvStore);
145 if (status == CRYPT_ERROR) {
146 ctxt->options.rebuild = true;
147 status = kvm->kvDataManager_.GetSingleKvStore(ctxt->options, appId, storeId, kvStore);
148 ZLOGE("Data has corrupted, rebuild db");
149 }
150
151 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
152 napi_ok : napi_generic_failure;
153 ctxt->kvStore->SetKvStorePtr(kvStore);
154 ctxt->kvStore->SetSchemaInfo(!ctxt->options.schema.empty());
155 ctxt->kvStore->SetContextParam(kvm->param_);
156 ctxt->kvStore->SetUvQueue(kvm->uvQueue_);
157 };
158 auto output = [env, ctxt](napi_value& result) {
159 ctxt->status = napi_get_reference_value(env, ctxt->ref, &result);
160 napi_delete_reference(env, ctxt->ref);
161 ASSERT_STATUS(ctxt, "output KVManager failed");
162 };
163 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
164 }
165
166 /*
167 * [JS API Prototype]
168 * [AsyncCB] closeKVStore(appId: string, storeId: string, kvStore: KVStore, callback: AsyncCallback<void>):void
169 * [Promise] closeKVStore(appId: string, storeId: string, kvStore: KVStore):Promise<void>
170 */
CloseKVStore(napi_env env,napi_callback_info info)171 napi_value JsKVManager::CloseKVStore(napi_env env, napi_callback_info info)
172 {
173 struct ContextInfo : public ContextBase {
174 std::string appId;
175 std::string storeId;
176 napi_value kvStore;
177 };
178 auto ctxt = std::make_shared<ContextInfo>();
179 auto input = [env, ctxt](size_t argc, napi_value* argv) {
180 // required 3 arguments :: <appId> <storeId> <kvStore>
181 ASSERT_BUSINESS_ERR(ctxt, argc >= 2, Status::INVALID_ARGUMENT, "The number of parameters is incorrect.");
182 ctxt->status = JSUtil::GetValue(env, argv[0], ctxt->appId);
183 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && !ctxt->appId.empty(), Status::INVALID_ARGUMENT,
184 "The type of appId must be string.");
185 ctxt->status = JSUtil::GetValue(env, argv[1], ctxt->storeId);
186 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && !ctxt->storeId.empty(), Status::INVALID_ARGUMENT,
187 "The type of storeId must be string.");
188 };
189 ctxt->GetCbInfo(env, info, input);
190 ASSERT_NULL(!ctxt->isThrowError, "CloseKVStore exits");
191
192 auto execute = [ctxt]() {
193 AppId appId { ctxt->appId };
194 StoreId storeId { ctxt->storeId };
195 Status status = reinterpret_cast<JsKVManager*>(ctxt->native)->kvDataManager_.CloseKvStore(appId, storeId);
196 status = GenerateNapiError(status, ctxt->jsCode, ctxt->error);
197 ZLOGD("CloseKVStore return status:%{public}d", status);
198 ctxt->status
199 = (status == Status::SUCCESS) || (status == Status::STORE_NOT_FOUND) || (status == Status::STORE_NOT_OPEN)
200 ? napi_ok
201 : napi_generic_failure;
202 };
203 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute);
204 }
205
206 /*
207 * [JS API Prototype]
208 * [AsyncCB] deleteKVStore(appId: string, storeId: string, callback: AsyncCallback<void>): void
209 * [Promise] deleteKVStore(appId: string, storeId: string):Promise<void>
210 */
DeleteKVStore(napi_env env,napi_callback_info info)211 napi_value JsKVManager::DeleteKVStore(napi_env env, napi_callback_info info)
212 {
213 struct ContextInfo : public ContextBase {
214 std::string appId;
215 std::string storeId;
216 };
217 auto ctxt = std::make_shared<ContextInfo>();
218 auto input = [env, ctxt](size_t argc, napi_value* argv) {
219 // required 2 arguments :: <appId> <storeId>
220 ASSERT_BUSINESS_ERR(ctxt, argc >= 2, Status::INVALID_ARGUMENT, "The number of parameters is incorrect.");
221 size_t index = 0;
222 ctxt->status = JSUtil::GetValue(env, argv[index++], ctxt->appId);
223 ASSERT_BUSINESS_ERR(ctxt, !ctxt->appId.empty(), Status::INVALID_ARGUMENT,
224 "The parameters of appId is incorrect.");
225 ctxt->status = JSUtil::GetValue(env, argv[index++], ctxt->storeId);
226 ASSERT_BUSINESS_ERR(ctxt, !ctxt->storeId.empty(), Status::INVALID_ARGUMENT,
227 "The parameters of storeId is incorrect.");
228 };
229 ctxt->GetCbInfo(env, info, input);
230 ASSERT_NULL(!ctxt->isThrowError, "DeleteKVStore exits");
231
232 auto execute = [ctxt]() {
233 AppId appId { ctxt->appId };
234 StoreId storeId { ctxt->storeId };
235 auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
236 ASSERT_ARGS(ctxt, kvm != nullptr, "KVManager is null, failed!");
237 std::string databaseDir = kvm->param_->baseDir;
238 ZLOGD("DeleteKVStore databaseDir is: %{public}s", databaseDir.c_str());
239 Status status = kvm->kvDataManager_.DeleteKvStore(appId, storeId, databaseDir);
240 ZLOGD("DeleteKvStore status:%{public}d", status);
241 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
242 napi_ok : napi_generic_failure;
243 };
244 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute);
245 }
246
247 /*
248 * [JS API Prototype]
249 * [AsyncCB] getAllKVStoreId(appId: string, callback: AsyncCallback<string[]>):void
250 * [Promise] getAllKVStoreId(appId: string):Promise<string[]>
251 */
GetAllKVStoreId(napi_env env,napi_callback_info info)252 napi_value JsKVManager::GetAllKVStoreId(napi_env env, napi_callback_info info)
253 {
254 struct ContextInfo : public ContextBase {
255 std::string appId;
256 std::vector<StoreId> storeIdList;
257 };
258
259 auto ctxt = std::make_shared<ContextInfo>();
260 auto input = [env, ctxt](size_t argc, napi_value* argv) {
261 // required 1 arguments :: <appId>
262 ASSERT_BUSINESS_ERR(ctxt, argc >= 1, Status::INVALID_ARGUMENT, "The number of parameters is incorrect.");
263 ctxt->status = JSUtil::GetValue(env, argv[0], ctxt->appId);
264 ASSERT_BUSINESS_ERR(ctxt, !ctxt->appId.empty(), Status::INVALID_ARGUMENT,
265 "The parameters of appId is incorrect.");
266 };
267 ctxt->GetCbInfo(env, info, input);
268 ASSERT_NULL(!ctxt->isThrowError, "GetAllKVStoreId exits");
269
270 auto execute = [ctxt]() {
271 auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
272 ASSERT_ARGS(ctxt, kvm != nullptr, "KVManager is null, failed!");
273 AppId appId { ctxt->appId };
274 Status status = kvm->kvDataManager_.GetAllKvStoreId(appId, ctxt->storeIdList);
275 ZLOGD("execute status:%{public}d", status);
276 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
277 napi_ok : napi_generic_failure;
278 };
279 auto output = [env, ctxt](napi_value& result) {
280 ctxt->status = JSUtil::SetValue(env, ctxt->storeIdList, result);
281 ZLOGD("output status:%{public}d", ctxt->status);
282 };
283 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
284 }
285
On(napi_env env,napi_callback_info info)286 napi_value JsKVManager::On(napi_env env, napi_callback_info info)
287 {
288 auto ctxt = std::make_shared<ContextBase>();
289 auto input = [env, ctxt](size_t argc, napi_value* argv) {
290 // required 2 arguments :: <event> <callback>
291 ASSERT_BUSINESS_ERR(ctxt, argc >= 2, Status::INVALID_ARGUMENT, "The number of parameters is incorrect.");
292 std::string event;
293 ctxt->status = JSUtil::GetValue(env, argv[0], event);
294 ZLOGI("subscribe to event:%{public}s", event.c_str());
295 ASSERT_BUSINESS_ERR(ctxt, event == "distributedDataServiceDie", Status::INVALID_ARGUMENT,
296 "The parameters of event is incorrect.");
297
298 napi_valuetype valueType = napi_undefined;
299 ctxt->status = napi_typeof(env, argv[1], &valueType);
300 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && (valueType == napi_function), Status::INVALID_ARGUMENT,
301 "The type of parameters deathCallback must be a function.");
302
303 JsKVManager* proxy = reinterpret_cast<JsKVManager*>(ctxt->native);
304 ASSERT_BUSINESS_ERR(ctxt, proxy != nullptr, Status::INVALID_ARGUMENT, "there is no native kv manager.");
305
306 std::lock_guard<std::mutex> lck(proxy->deathMutex_);
307 for (auto& it : proxy->deathRecipient_) {
308 if (JSUtil::Equals(env, argv[1], it->GetCallback())) {
309 ZLOGD("KVManager::On callback already register!");
310 return;
311 }
312 }
313 auto deathRecipient = std::make_shared<DeathRecipient>(proxy->uvQueue_, argv[1]);
314 proxy->kvDataManager_.RegisterKvStoreServiceDeathRecipient(deathRecipient);
315 proxy->deathRecipient_.push_back(deathRecipient);
316 ZLOGD("on mapsize: %{public}d", static_cast<int>(proxy->deathRecipient_.size()));
317 };
318 ctxt->GetCbInfoSync(env, info, input);
319 if (ctxt->status != napi_ok) {
320 ThrowNapiError(env, Status::INVALID_ARGUMENT, "");
321 }
322 return nullptr;
323 }
324
Off(napi_env env,napi_callback_info info)325 napi_value JsKVManager::Off(napi_env env, napi_callback_info info)
326 {
327 auto ctxt = std::make_shared<ContextBase>();
328 auto input = [env, ctxt](size_t argc, napi_value* argv) {
329 // required 1 or 2 arguments :: <event> [callback]
330 ASSERT_BUSINESS_ERR(ctxt, argc > 0, Status::INVALID_ARGUMENT, "The number of parameters is incorrect.");
331 std::string event;
332 ctxt->status = JSUtil::GetValue(env, argv[0], event);
333 // required 1 arguments :: <event>
334 ZLOGI("unsubscribe to event:%{public}s %{public}s specified", event.c_str(), (argc == 1) ? "without" : "with");
335 ASSERT_BUSINESS_ERR(ctxt, event == "distributedDataServiceDie", Status::INVALID_ARGUMENT,
336 "The parameters of event is incorrect.");
337 // have 2 arguments :: have the [callback]
338 if (argc == 2) {
339 napi_valuetype valueType = napi_undefined;
340 ctxt->status = napi_typeof(env, argv[1], &valueType);
341 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && (valueType == napi_function),
342 Status::INVALID_ARGUMENT, "The type of parameters deathCallback must be a function.");
343 }
344 JsKVManager* proxy = reinterpret_cast<JsKVManager*>(ctxt->native);
345 std::lock_guard<std::mutex> lck(proxy->deathMutex_);
346 auto it = proxy->deathRecipient_.begin();
347 while (it != proxy->deathRecipient_.end()) {
348 // have 2 arguments :: have the [callback]
349 if ((argc == 1) || JSUtil::Equals(env, argv[1], (*it)->GetCallback())) {
350 proxy->kvDataManager_.UnRegisterKvStoreServiceDeathRecipient(*it);
351 (*it)->Clear();
352 it = proxy->deathRecipient_.erase(it);
353 } else {
354 ++it;
355 }
356 }
357 ZLOGD("off mapsize: %{public}d", static_cast<int>(proxy->deathRecipient_.size()));
358 };
359 ctxt->GetCbInfoSync(env, info, input);
360 if (ctxt->status != napi_ok) {
361 ThrowNapiError(env, Status::INVALID_ARGUMENT, "");
362 }
363 ZLOGD("KVManager::Off callback is not register or already unregister!");
364 return nullptr;
365 }
366
Constructor(napi_env env)367 napi_value JsKVManager::Constructor(napi_env env)
368 {
369 const napi_property_descriptor properties[] = {
370 DECLARE_NAPI_FUNCTION("getKVStore", JsKVManager::GetKVStore),
371 DECLARE_NAPI_FUNCTION("closeKVStore", JsKVManager::CloseKVStore),
372 DECLARE_NAPI_FUNCTION("deleteKVStore", JsKVManager::DeleteKVStore),
373 DECLARE_NAPI_FUNCTION("getAllKVStoreId", JsKVManager::GetAllKVStoreId),
374 DECLARE_NAPI_FUNCTION("on", JsKVManager::On),
375 DECLARE_NAPI_FUNCTION("off", JsKVManager::Off)
376 };
377 size_t count = sizeof(properties) / sizeof(properties[0]);
378 return JSUtil::DefineClass(env, "JsKVManager", properties, count, JsKVManager::New);
379 }
380
New(napi_env env,napi_callback_info info)381 napi_value JsKVManager::New(napi_env env, napi_callback_info info)
382 {
383 std::string bundleName;
384 ContextParam param;
385 auto ctxt = std::make_shared<ContextBase>();
386 auto input = [env, ctxt, &bundleName, ¶m](size_t argc, napi_value* argv) {
387 // required 1 arguments :: <bundleName>
388 ASSERT_BUSINESS_ERR(ctxt, argc >= 1, Status::INVALID_ARGUMENT, "The number of parameters is incorrect.");
389 ctxt->status = JSUtil::GetNamedProperty(env, argv[0], "bundleName", bundleName);
390 ASSERT_BUSINESS_ERR(ctxt, ctxt->status != napi_generic_failure, Status::INVALID_ARGUMENT,
391 "Missing bundleName parameter.");
392 ASSERT_BUSINESS_ERR(ctxt, !bundleName.empty(), Status::INVALID_ARGUMENT,
393 "The type of bundleName must be string.");
394
395 napi_value jsContext = nullptr;
396 JSUtil::GetNamedProperty(env, argv[0], "context", jsContext);
397 ctxt->status = JSUtil::GetValue(env, jsContext, param);
398 ASSERT_BUSINESS_ERR(ctxt, ctxt->status == napi_ok, Status::INVALID_ARGUMENT, "get context parameter failed.");
399 };
400 ctxt->GetCbInfoSync(env, info, input);
401 ASSERT_NULL(!ctxt->isThrowError, "JsKVManager New exit");
402
403 JsKVManager* kvManager = new (std::nothrow) JsKVManager(bundleName, env, param);
404 ASSERT_ERR(env, kvManager != nullptr, Status::INVALID_ARGUMENT, "no memory for kvManager.");
405
406 auto finalize = [](napi_env env, void* data, void* hint) {
407 ZLOGD("kvManager finalize.");
408 auto* kvManager = reinterpret_cast<JsKVManager*>(data);
409 ASSERT_VOID(kvManager != nullptr, "finalize null!");
410 delete kvManager;
411 };
412 ASSERT_CALL(env, napi_wrap(env, ctxt->self, kvManager, finalize, nullptr, nullptr), kvManager);
413 return ctxt->self;
414 }
415
OnRemoteDied()416 void JsKVManager::DeathRecipient::OnRemoteDied()
417 {
418 AsyncCall();
419 }
420 } // namespace OHOS::DistributedKVStore
421