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