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 
24 using namespace OHOS::DistributedKv;
25 
26 namespace OHOS::DistributedData {
IsStoreTypeSupported(Options options)27 bool IsStoreTypeSupported(Options options)
28 {
29     return (options.kvStoreType == KvStoreType::DEVICE_COLLABORATION)
30         || (options.kvStoreType == KvStoreType::SINGLE_VERSION);
31 }
32 
JsKVManager(const std::string & bundleName,napi_env env,ContextParam param)33 JsKVManager::JsKVManager(const std::string &bundleName, napi_env env, ContextParam param)
34     : bundleName_(bundleName), uvQueue_(std::make_shared<UvQueue>(env)),
35     param_(std::make_shared<ContextParam>(std::move(param)))
36 {
37 }
38 
~JsKVManager()39 JsKVManager::~JsKVManager()
40 {
41     ZLOGD("no memory leak for JsKVManager");
42     std::lock_guard<std::mutex> lck(deathMutex_);
43     for (auto& it : deathRecipient_) {
44         kvDataManager_.UnRegisterKvStoreServiceDeathRecipient(it);
45         it->Clear();
46     }
47     deathRecipient_.clear();
48 }
49 
50 /*
51  * [JS API Prototype]
52  * [AsyncCB]  createKVManager(config: KVManagerConfig, callback: AsyncCallback<JsKVManager>): void;
53  * [Promise]  createKVManager(config: KVManagerConfig) : Promise<JsKVManager>;
54  */
CreateKVManager(napi_env env,napi_callback_info info)55 napi_value JsKVManager::CreateKVManager(napi_env env, napi_callback_info info)
56 {
57     ZLOGD("CreateKVManager in");
58     struct ContextInfo : public ContextBase {
59         JsKVManager* kvManger = nullptr;
60         napi_ref ref = nullptr;
61     };
62     auto ctxt = std::make_shared<ContextInfo>();
63     auto input = [env, ctxt](size_t argc, napi_value* argv) {
64         // required 1 arguments :: <bundleName>
65         CHECK_ARGS_RETURN_VOID(ctxt, argc == 1, "invalid arguments!");
66         std::string bundleName;
67         ctxt->status = JSUtil::GetNamedProperty(env, argv[0], "bundleName", bundleName);
68         CHECK_ARGS_RETURN_VOID(ctxt, (ctxt->status == napi_ok) && !bundleName.empty(), "invalid bundleName!");
69         UserInfo userInfo;
70         ctxt->status = JSUtil::GetNamedProperty(env, argv[0], "userInfo", userInfo);
71         CHECK_ARGS_RETURN_VOID(ctxt, ctxt->status == napi_ok, "invalid userInfo!");
72 
73         ctxt->ref = JSUtil::NewWithRef(env, argc, argv, reinterpret_cast<void**>(&ctxt->kvManger),
74                                        JsKVManager::Constructor(env));
75         CHECK_ARGS_RETURN_VOID(ctxt, ctxt->kvManger != nullptr, "KVManager::New failed!");
76     };
77     ctxt->GetCbInfo(env, info, input);
78 
79     auto noExecute = NapiAsyncExecute();
80     auto output = [env, ctxt](napi_value& result) {
81         ctxt->status = napi_get_reference_value(env, ctxt->ref, &result);
82         napi_delete_reference(env, ctxt->ref);
83         CHECK_STATUS_RETURN_VOID(ctxt, "output KVManager failed");
84     };
85     return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), noExecute, output);
86 }
87 
88 struct GetKVStoreContext : public ContextBase {
89     std::string storeId;
90     Options options;
91     JsKVStore* kvStore = nullptr;
92     napi_ref ref = nullptr;
93 
GetCbInfoOHOS::DistributedData::GetKVStoreContext94     void GetCbInfo(napi_env env, napi_callback_info info)
95     {
96         auto input = [env, this](size_t argc, napi_value* argv) {
97             // required 2 arguments :: <storeId> <options>
98             CHECK_ARGS_RETURN_VOID(this, argc == 2, "invalid arguments!");
99             status = JSUtil::GetValue(env, argv[0], storeId);
100             CHECK_ARGS_RETURN_VOID(this, (status == napi_ok) && !storeId.empty(), "invalid storeId!");
101             status = JSUtil::GetValue(env, argv[1], options);
102             CHECK_STATUS_RETURN_VOID(this, "invalid options!");
103             CHECK_ARGS_RETURN_VOID(this, IsStoreTypeSupported(options), "invalid options.KvStoreType");
104             ZLOGD("GetKVStore kvStoreType=%{public}d", options.kvStoreType);
105             if (options.kvStoreType == KvStoreType::DEVICE_COLLABORATION) {
106                 ref = JSUtil::NewWithRef(env, argc, argv, reinterpret_cast<void**>(&kvStore),
107                                          JsDeviceKVStore::Constructor(env));
108             } else if (options.kvStoreType == KvStoreType::SINGLE_VERSION) {
109                 ref = JSUtil::NewWithRef(env, argc, argv, reinterpret_cast<void**>(&kvStore),
110                                          JsSingleKVStore::Constructor(env));
111             }
112         };
113         ContextBase::GetCbInfo(env, info, input);
114     }
115 };
116 
117 /*
118  * [JS API Prototype]
119  * [AsyncCallback]
120  *      getKVStore<T extends KVStore>(storeId: string, options: Options, callback: AsyncCallback<T>): void;
121  * [Promise]
122  *      getKVStore<T extends KVStore>(storeId: string, options: Options): Promise<T>;
123  */
GetKVStore(napi_env env,napi_callback_info info)124 napi_value JsKVManager::GetKVStore(napi_env env, napi_callback_info info)
125 {
126     ZLOGD("GetKVStore in");
127     auto ctxt = std::make_shared<GetKVStoreContext>();
128     ctxt->GetCbInfo(env, info);
129     auto execute = [ctxt]() {
130         auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
131         CHECK_ARGS_RETURN_VOID(ctxt, kvm != nullptr, "KVManager is null, failed!");
132         AppId appId = { kvm->bundleName_ };
133         StoreId storeId = { ctxt->storeId };
134         ctxt->options.baseDir = kvm->param_->baseDir;
135         ctxt->options.area = kvm->param_->area + 1;
136         ctxt->options.hapName = kvm->param_->hapName;
137         ZLOGD("Options area:%{public}d dir:%{public}s", ctxt->options.area, ctxt->options.baseDir.c_str());
138         std::shared_ptr<DistributedKv::SingleKvStore> kvStore;
139         Status status = kvm->kvDataManager_.GetSingleKvStore(ctxt->options, appId, storeId, kvStore);
140         if (status == CRYPT_ERROR) {
141             ctxt->options.rebuild = true;
142             status = kvm->kvDataManager_.GetSingleKvStore(ctxt->options, appId, storeId, kvStore);
143             ZLOGD("Data has corrupted, rebuild db");
144         }
145         ZLOGD("GetSingleKvStore status:%{public}d", status);
146         ctxt->status = (status == Status::SUCCESS) ? napi_ok : napi_generic_failure;
147         CHECK_STATUS_RETURN_VOID(ctxt, "GetSingleKvStore() failed!");
148         ctxt->kvStore->SetNative(kvStore);
149         ctxt->kvStore->SetSchemaInfo(!ctxt->options.schema.empty());
150         ctxt->kvStore->SetContextParam(kvm->param_);
151         ctxt->kvStore->SetUvQueue(kvm->uvQueue_);
152     };
153     auto output = [env, ctxt](napi_value& result) {
154         ctxt->status = napi_get_reference_value(env, ctxt->ref, &result);
155         CHECK_STATUS_RETURN_VOID(ctxt, "output get ref value failed");
156         ctxt->status = napi_delete_reference(env, ctxt->ref);
157         CHECK_STATUS_RETURN_VOID(ctxt, "output del ref failed");
158         ZLOGI("output del ref success");
159     };
160     return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
161 }
162 
163 /*
164  * [JS API Prototype]
165  * [AsyncCB]  closeKVStore(appId: string, storeId: string, kvStore: KVStore, callback: AsyncCallback<void>):void
166  * [Promise]  closeKVStore(appId: string, storeId: string, kvStore: KVStore):Promise<void>
167  */
CloseKVStore(napi_env env,napi_callback_info info)168 napi_value JsKVManager::CloseKVStore(napi_env env, napi_callback_info info)
169 {
170     ZLOGD("CloseKVStore in");
171     struct ContextInfo : public ContextBase {
172         std::string appId;
173         std::string storeId;
174         napi_value kvStore;
175     };
176     auto ctxt = std::make_shared<ContextInfo>();
177     auto input = [env, ctxt](size_t argc, napi_value* argv) {
178         // required 3 arguments :: <appId> <storeId> <kvStore>
179         CHECK_ARGS_RETURN_VOID(ctxt, argc == 3, "invalid arguments!");
180         ctxt->status = JSUtil::GetValue(env, argv[0], ctxt->appId);
181         CHECK_ARGS_RETURN_VOID(ctxt, (ctxt->status == napi_ok) && !ctxt->appId.empty(), "invalid appId!");
182         ctxt->status = JSUtil::GetValue(env, argv[1], ctxt->storeId);
183         CHECK_ARGS_RETURN_VOID(ctxt, (ctxt->status == napi_ok) && !ctxt->storeId.empty(), "invalid storeId!");
184         CHECK_ARGS_RETURN_VOID(ctxt, argv[2] != nullptr, "kvStore is nullptr!");
185         bool isSingle = JsKVStore::IsInstanceOf(env, argv[2], ctxt->storeId, JsSingleKVStore::Constructor(env));
186         bool isDevice = JsKVStore::IsInstanceOf(env, argv[2], ctxt->storeId, JsDeviceKVStore::Constructor(env));
187         CHECK_ARGS_RETURN_VOID(ctxt, isSingle || isDevice, "kvStore unmatch to storeId!");
188     };
189     ctxt->GetCbInfo(env, info, input);
190 
191     auto execute = [ctxt]() {
192         AppId appId { ctxt->appId };
193         StoreId storeId { ctxt->storeId };
194         Status status = reinterpret_cast<JsKVManager*>(ctxt->native)->kvDataManager_.CloseKvStore(appId, storeId);
195         ZLOGD("CloseKVStore return status:%{public}d", status);
196         ctxt->status
197             = ((status == Status::SUCCESS) || (status == Status::STORE_NOT_FOUND) || (status == Status::STORE_NOT_OPEN))
198             ? napi_ok
199             : napi_generic_failure;
200     };
201     return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute);
202 }
203 
204 /*
205  * [JS API Prototype]
206  * [AsyncCB]  deleteKVStore(appId: string, storeId: string, callback: AsyncCallback<void>): void
207  * [Promise]  deleteKVStore(appId: string, storeId: string):Promise<void>
208  */
DeleteKVStore(napi_env env,napi_callback_info info)209 napi_value JsKVManager::DeleteKVStore(napi_env env, napi_callback_info info)
210 {
211     ZLOGD("DeleteKVStore in");
212     struct ContextInfo : public ContextBase {
213         std::string appId;
214         std::string storeId;
215     };
216     auto ctxt = std::make_shared<ContextInfo>();
217     auto input = [env, ctxt](size_t argc, napi_value* argv) {
218         // required 2 arguments :: <appId> <storeId>
219         CHECK_ARGS_RETURN_VOID(ctxt, argc >= 2, "invalid arguments!");
220         size_t index = 0;
221         ctxt->status = JSUtil::GetValue(env, argv[index++], ctxt->appId);
222         CHECK_ARGS_RETURN_VOID(ctxt, !ctxt->appId.empty(), "invalid appId");
223         ctxt->status = JSUtil::GetValue(env, argv[index++], ctxt->storeId);
224         CHECK_ARGS_RETURN_VOID(ctxt, !ctxt->storeId.empty(), "invalid storeId");
225     };
226     ctxt->GetCbInfo(env, info, input);
227 
228     auto execute = [ctxt]() {
229         AppId appId { ctxt->appId };
230         StoreId storeId { ctxt->storeId };
231         auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
232         CHECK_ARGS_RETURN_VOID(ctxt, kvm != nullptr, "KVManager is null, failed!");
233         std::string databaseDir = kvm->param_->baseDir;
234         ZLOGD("DeleteKVStore databaseDir is: %{public}s", databaseDir.c_str());
235         Status status = kvm->kvDataManager_.DeleteKvStore(appId, storeId, databaseDir);
236         ZLOGD("DeleteKvStore status:%{public}d", status);
237         ctxt->status = (status == Status::SUCCESS) ? napi_ok : napi_generic_failure;
238     };
239     return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute);
240 }
241 
242 /*
243  * [JS API Prototype]
244  * [AsyncCB]  getAllKVStoreId(appId: string, callback: AsyncCallback<string[]>):void
245  * [Promise]  getAllKVStoreId(appId: string):Promise<string[]>
246  */
GetAllKVStoreId(napi_env env,napi_callback_info info)247 napi_value JsKVManager::GetAllKVStoreId(napi_env env, napi_callback_info info)
248 {
249     ZLOGD("GetAllKVStoreId in");
250     struct ContextInfo : public ContextBase {
251         std::string appId;
252         std::vector<StoreId> storeIdList;
253     };
254 
255     auto ctxt = std::make_shared<ContextInfo>();
256     auto input = [env, ctxt](size_t argc, napi_value* argv) {
257         // required 1 arguments :: <appId>
258         CHECK_ARGS_RETURN_VOID(ctxt, argc == 1, "invalid arguments!");
259         ctxt->status = JSUtil::GetValue(env, argv[0], ctxt->appId);
260         CHECK_ARGS_RETURN_VOID(ctxt, !ctxt->appId.empty(), "invalid appId!");
261     };
262     ctxt->GetCbInfo(env, info, input);
263 
264     auto execute = [ctxt]() {
265         auto kvm = reinterpret_cast<JsKVManager*>(ctxt->native);
266         CHECK_ARGS_RETURN_VOID(ctxt, kvm != nullptr, "KVManager is null, failed!");
267         AppId appId { ctxt->appId };
268         Status status = kvm->kvDataManager_.GetAllKvStoreId(appId, ctxt->storeIdList);
269         ZLOGD("execute status:%{public}d", status);
270         ctxt->status = (status == Status::SUCCESS) ? napi_ok : napi_generic_failure;
271     };
272     auto output = [env, ctxt](napi_value& result) {
273         ctxt->status = JSUtil::SetValue(env, ctxt->storeIdList, result);
274         ZLOGD("output status:%{public}d", ctxt->status);
275     };
276     return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
277 }
278 
On(napi_env env,napi_callback_info info)279 napi_value JsKVManager::On(napi_env env, napi_callback_info info)
280 {
281     auto ctxt = std::make_shared<ContextBase>();
282     auto input = [env, ctxt](size_t argc, napi_value* argv) {
283         // required 2 arguments :: <event> <callback>
284         CHECK_ARGS_RETURN_VOID(ctxt, argc == 2, "invalid arguments!");
285         std::string event;
286         ctxt->status = JSUtil::GetValue(env, argv[0], event);
287         ZLOGI("subscribe to event:%{public}s", event.c_str());
288         CHECK_ARGS_RETURN_VOID(ctxt, event == "distributedDataServiceDie", "invalid arg[0], i.e. invalid event!");
289 
290         napi_valuetype valueType = napi_undefined;
291         ctxt->status = napi_typeof(env, argv[1], &valueType);
292         CHECK_STATUS_RETURN_VOID(ctxt, "napi_typeof failed!");
293         CHECK_ARGS_RETURN_VOID(ctxt, valueType == napi_function, "callback is not a function");
294 
295         JsKVManager* proxy = reinterpret_cast<JsKVManager*>(ctxt->native);
296         CHECK_ARGS_RETURN_VOID(ctxt, proxy != nullptr, "there is no native kv manager");
297 
298         std::lock_guard<std::mutex> lck(proxy->deathMutex_);
299         for (auto& it : proxy->deathRecipient_) {
300             if (JSUtil::Equals(env, argv[1], it->GetCallback())) {
301                 ZLOGD("KVManager::On callback already register!");
302                 return;
303             }
304         }
305         auto deathRecipient = std::make_shared<DeathRecipient>(proxy->uvQueue_, argv[1]);
306         proxy->kvDataManager_.RegisterKvStoreServiceDeathRecipient(deathRecipient);
307         proxy->deathRecipient_.push_back(deathRecipient);
308         ZLOGD("on mapsize: %{public}d", static_cast<int>(proxy->deathRecipient_.size()));
309     };
310     ctxt->GetCbInfoSync(env, info, input);
311     NAPI_ASSERT(env, ctxt->status == napi_ok, "invalid arguments!");
312     return nullptr;
313 }
314 
Off(napi_env env,napi_callback_info info)315 napi_value JsKVManager::Off(napi_env env, napi_callback_info info)
316 {
317     ZLOGD("KVManager::Off()");
318     auto ctxt = std::make_shared<ContextBase>();
319     auto input = [env, ctxt](size_t argc, napi_value* argv) {
320         // required 1 or 2 arguments :: <event> [callback]
321         CHECK_ARGS_RETURN_VOID(ctxt, (argc == 1) || (argc == 2), "invalid arguments!");
322         std::string event;
323         ctxt->status = JSUtil::GetValue(env, argv[0], event);
324         // required 1 arguments :: <event>
325         ZLOGI("unsubscribe to event:%{public}s %{public}s specified", event.c_str(), (argc == 1) ? "without": "with");
326         CHECK_ARGS_RETURN_VOID(ctxt, event == "distributedDataServiceDie", "invalid arg[0], i.e. invalid event!");
327         // have 2 arguments :: have the [callback]
328         if (argc == 2) {
329             napi_valuetype valueType = napi_undefined;
330             ctxt->status = napi_typeof(env, argv[1], &valueType);
331             CHECK_STATUS_RETURN_VOID(ctxt, "napi_typeof failed!");
332             CHECK_ARGS_RETURN_VOID(ctxt, valueType == napi_function, "callback is not a function");
333         }
334         JsKVManager* proxy = reinterpret_cast<JsKVManager*>(ctxt->native);
335         std::lock_guard<std::mutex> lck(proxy->deathMutex_);
336         auto it = proxy->deathRecipient_.begin();
337         while (it != proxy->deathRecipient_.end()) {
338             // have 2 arguments :: have the [callback]
339             if ((argc == 1) || JSUtil::Equals(env, argv[1], (*it)->GetCallback())) {
340                 proxy->kvDataManager_.UnRegisterKvStoreServiceDeathRecipient(*it);
341                 (*it)->Clear();
342                 it = proxy->deathRecipient_.erase(it);
343             } else {
344                 ++it;
345             }
346         }
347         ZLOGD("off mapsize: %{public}d", static_cast<int>(proxy->deathRecipient_.size()));
348     };
349     ctxt->GetCbInfoSync(env, info, input);
350     NAPI_ASSERT(env, ctxt->status == napi_ok, "invalid arguments!");
351     ZLOGD("KVManager::Off callback is not register or already unregister!");
352     return nullptr;
353 }
354 
Constructor(napi_env env)355 napi_value JsKVManager::Constructor(napi_env env)
356 {
357     const napi_property_descriptor properties[] = {
358         DECLARE_NAPI_FUNCTION("getKVStore", JsKVManager::GetKVStore),
359         DECLARE_NAPI_FUNCTION("closeKVStore", JsKVManager::CloseKVStore),
360         DECLARE_NAPI_FUNCTION("deleteKVStore", JsKVManager::DeleteKVStore),
361         DECLARE_NAPI_FUNCTION("getAllKVStoreId", JsKVManager::GetAllKVStoreId),
362         DECLARE_NAPI_FUNCTION("on", JsKVManager::On),
363         DECLARE_NAPI_FUNCTION("off", JsKVManager::Off)
364     };
365     size_t count = sizeof(properties) / sizeof(properties[0]);
366     return JSUtil::DefineClass(env, "KVManager", properties, count, JsKVManager::New);
367 }
368 
New(napi_env env,napi_callback_info info)369 napi_value JsKVManager::New(napi_env env, napi_callback_info info)
370 {
371     std::string bundleName;
372     ContextParam param;
373     auto ctxt = std::make_shared<ContextBase>();
374     auto input = [env, ctxt, &bundleName, &param](size_t argc, napi_value* argv) {
375         // required 1 arguments :: <bundleName>
376         CHECK_ARGS_RETURN_VOID(ctxt, argc == 1, "invalid arguments!");
377         ctxt->status = JSUtil::GetNamedProperty(env, argv[0], "bundleName", bundleName);
378         CHECK_STATUS_RETURN_VOID(ctxt, "invalid arg[0], i.e. invalid bundleName!");
379         CHECK_ARGS_RETURN_VOID(ctxt, !bundleName.empty(), "invalid arg[0], i.e. invalid bundleName!");
380 
381         napi_value jsContext = nullptr;
382         JSUtil::GetNamedProperty(env, argv[0], "context", jsContext);
383         ctxt->status = JSUtil::GetValue(env, jsContext, param);
384         CHECK_ARGS_RETURN_VOID(ctxt, ctxt->status == napi_ok, "get context failed");
385     };
386     ctxt->GetCbInfoSync(env, info, input);
387     NAPI_ASSERT(env, ctxt->status == napi_ok, "invalid arguments!");
388 
389     JsKVManager* kvManager = new (std::nothrow) JsKVManager(bundleName, env, param);
390     NAPI_ASSERT(env, kvManager !=nullptr, "no memory for kvManager");
391 
392     auto finalize = [](napi_env env, void* data, void* hint) {
393         ZLOGD("kvManager finalize.");
394         auto* kvManager = reinterpret_cast<JsKVManager*>(data);
395         CHECK_RETURN_VOID(kvManager != nullptr, "kvManager is null!");
396         delete kvManager;
397     };
398     ASSERT_CALL(env, napi_wrap(env, ctxt->self, kvManager, finalize, nullptr, nullptr), kvManager);
399     return ctxt->self;
400 }
401 
OnRemoteDied()402 void JsKVManager::DeathRecipient::OnRemoteDied()
403 {
404     AsyncCall();
405 }
406 } // namespace OHOS::DistributedData
407