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, "Parameter error:Mandatory parameters are left unspecified");
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, "Parameter error:Missing bundleName parameter.");
67 return result;
68 }
69 if (bundleName.empty()) {
70 ThrowNapiError(env, Status::INVALID_ARGUMENT, "Parameter error: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, "Parameter error: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,
101 "Parameter error:Mandatory parameters are left unspecified");
102 status = JSUtil::GetValue(env, argv[0], storeId);
103 ASSERT_BUSINESS_ERR(this, ((status == napi_ok) && JSUtil::IsValid(storeId)), Status::INVALID_ARGUMENT,
104 "Parameter error:storeId must be string,consist of letters,digits,underscores(_),limit 128 chars");
105 status = JSUtil::GetValue(env, argv[1], options);
106 ASSERT_BUSINESS_ERR(this, status == napi_ok, Status::INVALID_ARGUMENT,
107 "Parameter error:The params type not matching option");
108 ASSERT_BUSINESS_ERR(this, options.securityLevel != INVALID_LABEL, Status::INVALID_ARGUMENT,
109 "Parameter error:unusable securityLevel");
110 ASSERT_BUSINESS_ERR(this, IsStoreTypeSupported(options), Status::INVALID_ARGUMENT,
111 "Parameter error:only support DEVICE_COLLABORATION or SINGLE_VERSION");
112 ZLOGD("GetKVStore kvStoreType=%{public}d", options.kvStoreType);
113 if (options.kvStoreType == KvStoreType::DEVICE_COLLABORATION) {
114 ref = JSUtil::NewWithRef(env, argc, argv, reinterpret_cast<void**>(&kvStore),
115 JsDeviceKVStore::Constructor(env));
116 } else if (options.kvStoreType == KvStoreType::SINGLE_VERSION) {
117 ref = JSUtil::NewWithRef(env, argc, argv, reinterpret_cast<void**>(&kvStore),
118 JsSingleKVStore::Constructor(env));
119 }
120 };
121 ContextBase::GetCbInfo(env, info, input);
122 }
123 };
124
125 /*
126 * [JS API Prototype]
127 * [AsyncCallback]
128 * getKVStore<T extends KVStore>(storeId: string, options: Options, callback: AsyncCallback<T>): void;
129 * [Promise]
130 * getKVStore<T extends KVStore>(storeId: string, options: Options): Promise<T>;
131 */
GetKVStore(napi_env env,napi_callback_info info)132 napi_value JsKVManager::GetKVStore(napi_env env, napi_callback_info info)
133 {
134 auto ctxt = std::make_shared<GetKVStoreContext>();
135 ctxt->GetCbInfo(env, info);
136 ASSERT_NULL(!ctxt->isThrowError, "GetKVStore exit");
137
138 auto execute = [ctxt]() {
139 auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
140 ASSERT_ARGS(ctxt, kvm != nullptr, "KVManager is null, failed!");
141 AppId appId = { kvm->bundleName_ };
142 StoreId storeId = { ctxt->storeId };
143 ctxt->options.baseDir = kvm->param_->baseDir;
144 ctxt->options.area = kvm->param_->area + 1;
145 ctxt->options.hapName = kvm->param_->hapName;
146 ctxt->options.apiVersion = kvm->param_->apiVersion;
147 ZLOGD("Options area:%{public}d dir:%{public}s", ctxt->options.area,
148 JSUtil::Anonymous(ctxt->options.baseDir).c_str());
149 std::shared_ptr<DistributedKv::SingleKvStore> kvStore;
150 Status status = kvm->kvDataManager_.GetSingleKvStore(ctxt->options, appId, storeId, kvStore);
151 if (status == DATA_CORRUPTED) {
152 ctxt->options.rebuild = true;
153 status = kvm->kvDataManager_.GetSingleKvStore(ctxt->options, appId, storeId, kvStore);
154 ZLOGE("Data has corrupted, rebuild db");
155 }
156 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
157 napi_ok : napi_generic_failure;
158 ctxt->kvStore->SetKvStorePtr(kvStore);
159 ctxt->kvStore->SetSchemaInfo(!ctxt->options.schema.empty());
160 ctxt->kvStore->SetContextParam(kvm->param_);
161 ctxt->kvStore->SetUvQueue(kvm->uvQueue_);
162 };
163 auto output = [env, ctxt](napi_value& result) {
164 ctxt->status = napi_get_reference_value(env, ctxt->ref, &result);
165 napi_delete_reference(env, ctxt->ref);
166 ASSERT_STATUS(ctxt, "output KVManager failed");
167 ZLOGI("output delete reference success");
168 };
169 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
170 }
171
172 /*
173 * [JS API Prototype]
174 * [AsyncCB] closeKVStore(appId: string, storeId: string, kvStore: KVStore, callback: AsyncCallback<void>):void
175 * [Promise] closeKVStore(appId: string, storeId: string, kvStore: KVStore):Promise<void>
176 */
CloseKVStore(napi_env env,napi_callback_info info)177 napi_value JsKVManager::CloseKVStore(napi_env env, napi_callback_info info)
178 {
179 struct ContextInfo : public ContextBase {
180 std::string appId;
181 std::string storeId;
182 napi_value kvStore;
183 };
184 auto ctxt = std::make_shared<ContextInfo>();
185 auto input = [env, ctxt](size_t argc, napi_value* argv) {
186 // required 3 arguments :: <appId> <storeId> <kvStore>
187 ASSERT_BUSINESS_ERR(ctxt, argc >= 2, Status::INVALID_ARGUMENT,
188 "Parameter error:Mandatory parameters are left unspecified");
189 ctxt->status = JSUtil::GetValue(env, argv[0], ctxt->appId);
190 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && !ctxt->appId.empty(), Status::INVALID_ARGUMENT,
191 "Parameter error:appId empty");
192 ctxt->status = JSUtil::GetValue(env, argv[1], ctxt->storeId);
193 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && JSUtil::IsValid(ctxt->storeId), Status::INVALID_ARGUMENT,
194 "Parameter error:storeId must be string,consist of letters, digits, underscores(_), limit 128 characters");
195 };
196 ctxt->GetCbInfo(env, info, input);
197 ASSERT_NULL(!ctxt->isThrowError, "CloseKVStore exits");
198
199 auto execute = [ctxt]() {
200 AppId appId { ctxt->appId };
201 StoreId storeId { ctxt->storeId };
202 Status status = reinterpret_cast<JsKVManager*>(ctxt->native)->kvDataManager_.CloseKvStore(appId, storeId);
203 status = GenerateNapiError(status, ctxt->jsCode, ctxt->error);
204 ZLOGD("CloseKVStore return status:%{public}d", status);
205 ctxt->status
206 = (status == Status::SUCCESS) || (status == Status::STORE_NOT_FOUND) || (status == Status::STORE_NOT_OPEN)
207 ? napi_ok
208 : napi_generic_failure;
209 };
210 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute);
211 }
212
213 /*
214 * [JS API Prototype]
215 * [AsyncCB] deleteKVStore(appId: string, storeId: string, callback: AsyncCallback<void>): void
216 * [Promise] deleteKVStore(appId: string, storeId: string):Promise<void>
217 */
DeleteKVStore(napi_env env,napi_callback_info info)218 napi_value JsKVManager::DeleteKVStore(napi_env env, napi_callback_info info)
219 {
220 struct ContextInfo : public ContextBase {
221 std::string appId;
222 std::string storeId;
223 };
224 auto ctxt = std::make_shared<ContextInfo>();
225 auto input = [env, ctxt](size_t argc, napi_value* argv) {
226 // required 2 arguments :: <appId> <storeId>
227 ASSERT_BUSINESS_ERR(ctxt, argc >= 2, Status::INVALID_ARGUMENT,
228 "Parameter error:Mandatory parameters are left unspecified");
229 size_t index = 0;
230 ctxt->status = JSUtil::GetValue(env, argv[index++], ctxt->appId);
231 ASSERT_BUSINESS_ERR(ctxt, !ctxt->appId.empty(), Status::INVALID_ARGUMENT,
232 "Parameter error:appId empty");
233 ctxt->status = JSUtil::GetValue(env, argv[index++], ctxt->storeId);
234 ASSERT_BUSINESS_ERR(ctxt, JSUtil::IsValid(ctxt->storeId), Status::INVALID_ARGUMENT,
235 "error:storeId must be string; consist of only letters, digits underscores (_),limit 128 characters");
236 };
237 ctxt->GetCbInfo(env, info, input);
238 ASSERT_NULL(!ctxt->isThrowError, "DeleteKVStore exits");
239
240 auto execute = [ctxt]() {
241 AppId appId { ctxt->appId };
242 StoreId storeId { ctxt->storeId };
243 auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
244 ASSERT_ARGS(ctxt, kvm != nullptr, "KVManager is null, failed!");
245 std::string databaseDir = kvm->param_->baseDir;
246 ZLOGD("DeleteKVStore databaseDir is: %{public}s", JSUtil::Anonymous(databaseDir).c_str());
247 Status status = kvm->kvDataManager_.DeleteKvStore(appId, storeId, databaseDir);
248 ZLOGD("DeleteKvStore status:%{public}d", status);
249 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
250 napi_ok : napi_generic_failure;
251 };
252 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute);
253 }
254
255 /*
256 * [JS API Prototype]
257 * [AsyncCB] getAllKVStoreId(appId: string, callback: AsyncCallback<string[]>):void
258 * [Promise] getAllKVStoreId(appId: string):Promise<string[]>
259 */
GetAllKVStoreId(napi_env env,napi_callback_info info)260 napi_value JsKVManager::GetAllKVStoreId(napi_env env, napi_callback_info info)
261 {
262 struct ContextInfo : public ContextBase {
263 std::string appId;
264 std::vector<StoreId> storeIdList;
265 };
266
267 auto ctxt = std::make_shared<ContextInfo>();
268 auto input = [env, ctxt](size_t argc, napi_value* argv) {
269 // required 1 arguments :: <appId>
270 ASSERT_BUSINESS_ERR(ctxt, argc >= 1, Status::INVALID_ARGUMENT,
271 "Parameter error:Mandatory parameters are left unspecified");
272 ctxt->status = JSUtil::GetValue(env, argv[0], ctxt->appId);
273 ASSERT_BUSINESS_ERR(ctxt, !ctxt->appId.empty(), Status::INVALID_ARGUMENT,
274 "Parameter error:appId empty");
275 ASSERT_BUSINESS_ERR(ctxt, (ctxt->appId.size() < MAX_APP_ID_LEN), Status::INVALID_ARGUMENT,
276 "Parameter error:appId exceed 256 characters");
277 };
278 ctxt->GetCbInfo(env, info, input);
279 ASSERT_NULL(!ctxt->isThrowError, "GetAllKVStoreId exits");
280
281 auto execute = [ctxt]() {
282 auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
283 ASSERT_ARGS(ctxt, kvm != nullptr, "KVManager is null, failed!");
284 AppId appId { ctxt->appId };
285 Status status = kvm->kvDataManager_.GetAllKvStoreId(appId, ctxt->storeIdList);
286 ZLOGD("execute status:%{public}d", status);
287 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
288 napi_ok : napi_generic_failure;
289 };
290 auto output = [env, ctxt](napi_value& result) {
291 ctxt->status = JSUtil::SetValue(env, ctxt->storeIdList, result);
292 ZLOGD("output status:%{public}d", ctxt->status);
293 };
294 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
295 }
296
On(napi_env env,napi_callback_info info)297 napi_value JsKVManager::On(napi_env env, napi_callback_info info)
298 {
299 auto ctxt = std::make_shared<ContextBase>();
300 auto input = [env, ctxt](size_t argc, napi_value* argv) {
301 // required 2 arguments :: <event> <callback>
302 ASSERT_BUSINESS_ERR(ctxt, argc >= 2, Status::INVALID_ARGUMENT,
303 "Parameter error:Mandatory parameters are left unspecified");
304 std::string event;
305 ctxt->status = JSUtil::GetValue(env, argv[0], event);
306 ZLOGI("subscribe to event:%{public}s", event.c_str());
307 ASSERT_BUSINESS_ERR(ctxt, event == "distributedDataServiceDie", Status::INVALID_ARGUMENT,
308 "Parameter error:parameter event type not equal distributedDataServiceDie");
309
310 napi_valuetype valueType = napi_undefined;
311 ctxt->status = napi_typeof(env, argv[1], &valueType);
312 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && (valueType == napi_function), Status::INVALID_ARGUMENT,
313 "Parameter error:parameter callback must be function type");
314
315 JsKVManager* proxy = reinterpret_cast<JsKVManager*>(ctxt->native);
316 ASSERT_BUSINESS_ERR(ctxt, proxy != nullptr, Status::INVALID_ARGUMENT,
317 "Parameter error:JsKVManager nullptr");
318
319 std::lock_guard<std::mutex> lck(proxy->deathMutex_);
320 for (auto& it : proxy->deathRecipient_) {
321 if (JSUtil::Equals(env, argv[1], it->GetCallback())) {
322 ZLOGD("KVManager::On callback already register!");
323 return;
324 }
325 }
326 auto deathRecipient = std::make_shared<DeathRecipient>(proxy->uvQueue_, argv[1]);
327 proxy->kvDataManager_.RegisterKvStoreServiceDeathRecipient(deathRecipient);
328 proxy->deathRecipient_.push_back(deathRecipient);
329 ZLOGD("on mapsize: %{public}d", static_cast<int>(proxy->deathRecipient_.size()));
330 };
331 ctxt->GetCbInfoSync(env, info, input);
332 if (ctxt->status != napi_ok) {
333 ThrowNapiError(env, Status::INVALID_ARGUMENT, "");
334 }
335 return nullptr;
336 }
337
Off(napi_env env,napi_callback_info info)338 napi_value JsKVManager::Off(napi_env env, napi_callback_info info)
339 {
340 auto ctxt = std::make_shared<ContextBase>();
341 auto input = [env, ctxt](size_t argc, napi_value* argv) {
342 // required 1 or 2 arguments :: <event> [callback]
343 ASSERT_BUSINESS_ERR(ctxt, argc > 0, Status::INVALID_ARGUMENT,
344 "Parameter error:Mandatory parameters are left unspecified");
345 std::string event;
346 ctxt->status = JSUtil::GetValue(env, argv[0], event);
347 // required 1 arguments :: <event>
348 ZLOGI("unsubscribe to event:%{public}s %{public}s specified", event.c_str(), (argc == 1) ? "without" : "with");
349 ASSERT_BUSINESS_ERR(ctxt, event == "distributedDataServiceDie", Status::INVALID_ARGUMENT,
350 "Parameter error:parameter event not equal distributedDataServiceDie");
351 // have 2 arguments :: have the [callback]
352 if (argc == 2) {
353 napi_valuetype valueType = napi_undefined;
354 ctxt->status = napi_typeof(env, argv[1], &valueType);
355 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && (valueType == napi_function),
356 Status::INVALID_ARGUMENT, "Parameter error:parameter callback type must be function");
357 }
358 JsKVManager* proxy = reinterpret_cast<JsKVManager*>(ctxt->native);
359 std::lock_guard<std::mutex> lck(proxy->deathMutex_);
360 auto it = proxy->deathRecipient_.begin();
361 while (it != proxy->deathRecipient_.end()) {
362 // have 2 arguments :: have the [callback]
363 if ((argc == 1) || JSUtil::Equals(env, argv[1], (*it)->GetCallback())) {
364 proxy->kvDataManager_.UnRegisterKvStoreServiceDeathRecipient(*it);
365 (*it)->Clear();
366 it = proxy->deathRecipient_.erase(it);
367 } else {
368 ++it;
369 }
370 }
371 ZLOGD("off mapsize: %{public}d", static_cast<int>(proxy->deathRecipient_.size()));
372 };
373 ctxt->GetCbInfoSync(env, info, input);
374 if (ctxt->status != napi_ok) {
375 ThrowNapiError(env, Status::INVALID_ARGUMENT, "Parameter error:params must be sting or function");
376 }
377 ZLOGD("KVManager::Off callback is not register or already unregister!");
378 return nullptr;
379 }
380
Constructor(napi_env env)381 napi_value JsKVManager::Constructor(napi_env env)
382 {
383 auto lambda = []() -> std::vector<napi_property_descriptor> {
384 std::vector<napi_property_descriptor> properties = {
385 DECLARE_NAPI_FUNCTION("getKVStore", JsKVManager::GetKVStore),
386 DECLARE_NAPI_FUNCTION("closeKVStore", JsKVManager::CloseKVStore),
387 DECLARE_NAPI_FUNCTION("deleteKVStore", JsKVManager::DeleteKVStore),
388 DECLARE_NAPI_FUNCTION("getAllKVStoreId", JsKVManager::GetAllKVStoreId),
389 DECLARE_NAPI_FUNCTION("on", JsKVManager::On),
390 DECLARE_NAPI_FUNCTION("off", JsKVManager::Off),
391 };
392 return properties;
393 };
394 return JSUtil::DefineClass(env, "ohos.data.distributedKVStore", "JsKVManager", lambda, JsKVManager::New);
395 }
396
New(napi_env env,napi_callback_info info)397 napi_value JsKVManager::New(napi_env env, napi_callback_info info)
398 {
399 std::string bundleName;
400 ContextParam param;
401 auto ctxt = std::make_shared<ContextBase>();
402 auto input = [env, ctxt, &bundleName, ¶m](size_t argc, napi_value* argv) {
403 // required 1 arguments :: <bundleName>
404 ASSERT_BUSINESS_ERR(ctxt, argc >= 1, Status::INVALID_ARGUMENT,
405 "Parameter error:Mandatory parameters are left unspecified");
406 ctxt->status = JSUtil::GetNamedProperty(env, argv[0], "bundleName", bundleName);
407 ASSERT_BUSINESS_ERR(ctxt, ctxt->status != napi_generic_failure, Status::INVALID_ARGUMENT,
408 "Parameter error:The bundleName parameter is missing.");
409 ASSERT_BUSINESS_ERR(ctxt, !bundleName.empty(), Status::INVALID_ARGUMENT,
410 "Parameter error:The bundleName field cannot be empty.");
411
412 napi_value jsContext = nullptr;
413 JSUtil::GetNamedProperty(env, argv[0], "context", jsContext);
414 ctxt->status = JSUtil::GetValue(env, jsContext, param);
415 ASSERT_BUSINESS_ERR(ctxt, ctxt->status == napi_ok, Status::INVALID_ARGUMENT,
416 "Parameter error:get context failed");
417 };
418 ctxt->GetCbInfoSync(env, info, input);
419 ASSERT_NULL(!ctxt->isThrowError, "JsKVManager New exit");
420
421 JsKVManager* kvManager = new (std::nothrow) JsKVManager(bundleName, env, param);
422 ASSERT_ERR(env, kvManager != nullptr, Status::INVALID_ARGUMENT, "Parameter error:kvManager is nullptr");
423
424 auto finalize = [](napi_env env, void* data, void* hint) {
425 ZLOGD("kvManager finalize.");
426 auto* kvManager = reinterpret_cast<JsKVManager*>(data);
427 ASSERT_VOID(kvManager != nullptr, "kvManager is null!");
428 delete kvManager;
429 };
430 ASSERT_CALL(env, napi_wrap(env, ctxt->self, kvManager, finalize, nullptr, nullptr), kvManager);
431 return ctxt->self;
432 }
433
OnRemoteDied()434 void JsKVManager::DeathRecipient::OnRemoteDied()
435 {
436 AsyncCall();
437 }
438 } // namespace OHOS::DistributedKVStore
439