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