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 "JsDeviceKVStore"
16 #include "js_device_kv_store.h"
17 #include <iomanip>
18 #include "js_kv_store_resultset.h"
19 #include "js_query.h"
20 #include "js_util.h"
21 #include "log_print.h"
22 #include "napi_queue.h"
23 #include "uv_queue.h"
24 #include "distributed_kv_data_manager.h"
25
26 using namespace OHOS::DistributedKv;
27 using namespace OHOS::DataShare;
28 namespace OHOS::DistributedKVStore {
29 constexpr int DEVICEID_WIDTH = 4;
GetDeviceKey(const std::string & deviceId,const std::string & key)30 static std::string GetDeviceKey(const std::string& deviceId, const std::string& key)
31 {
32 std::ostringstream oss;
33 if (!deviceId.empty()) {
34 oss << std::setfill('0') << std::setw(DEVICEID_WIDTH) << deviceId.length() << deviceId;
35 }
36 oss << key;
37 return oss.str();
38 }
39
JsDeviceKVStore(const std::string & storeId)40 JsDeviceKVStore::JsDeviceKVStore(const std::string& storeId)
41 : JsSingleKVStore(storeId)
42 {
43 }
44
Constructor(napi_env env)45 napi_value JsDeviceKVStore::Constructor(napi_env env)
46 {
47 auto lambda = []() -> std::vector<napi_property_descriptor>{
48 std::vector<napi_property_descriptor> properties = {
49 DECLARE_NAPI_FUNCTION("put", JsSingleKVStore::Put),
50 DECLARE_NAPI_FUNCTION("delete", JsSingleKVStore::Delete),
51 DECLARE_NAPI_FUNCTION("putBatch", JsSingleKVStore::PutBatch),
52 DECLARE_NAPI_FUNCTION("deleteBatch", JsSingleKVStore::DeleteBatch),
53 DECLARE_NAPI_FUNCTION("startTransaction", JsSingleKVStore::StartTransaction),
54 DECLARE_NAPI_FUNCTION("commit", JsSingleKVStore::Commit),
55 DECLARE_NAPI_FUNCTION("rollback", JsSingleKVStore::Rollback),
56 DECLARE_NAPI_FUNCTION("enableSync", JsSingleKVStore::EnableSync),
57 DECLARE_NAPI_FUNCTION("setSyncRange", JsSingleKVStore::SetSyncRange),
58 DECLARE_NAPI_FUNCTION("backup", JsSingleKVStore::Backup),
59 DECLARE_NAPI_FUNCTION("restore", JsSingleKVStore::Restore),
60 /* JsDeviceKVStore externs JsSingleKVStore */
61 DECLARE_NAPI_FUNCTION("get", JsDeviceKVStore::Get),
62 DECLARE_NAPI_FUNCTION("getEntries", JsDeviceKVStore::GetEntries),
63 DECLARE_NAPI_FUNCTION("getResultSet", JsDeviceKVStore::GetResultSet),
64 DECLARE_NAPI_FUNCTION("getResultSize", JsDeviceKVStore::GetResultSize),
65 DECLARE_NAPI_FUNCTION("closeResultSet", JsSingleKVStore::CloseResultSet),
66 DECLARE_NAPI_FUNCTION("removeDeviceData", JsSingleKVStore::RemoveDeviceData),
67 DECLARE_NAPI_FUNCTION("sync", JsSingleKVStore::Sync),
68 DECLARE_NAPI_FUNCTION("on", JsSingleKVStore::OnEvent), /* same to JsSingleKVStore */
69 DECLARE_NAPI_FUNCTION("off", JsSingleKVStore::OffEvent) /* same to JsSingleKVStore */
70 };
71 return properties;
72 };
73 return JSUtil::DefineClass(env, "ohos.data.distributedKVStore", "DeviceKVStore", lambda, JsDeviceKVStore::New);
74 }
75
76 /*
77 * [JS API Prototype]
78 * [AsyncCallback]
79 * get(deviceId:string, key:string, callback:AsyncCallback<boolean|string|number|Uint8Array>):void;
80 * [Promise]
81 * get(deviceId:string, key:string):Promise<boolean|string|number|Uint8Array>;
82 */
Get(napi_env env,napi_callback_info info)83 napi_value JsDeviceKVStore::Get(napi_env env, napi_callback_info info)
84 {
85 struct GetContext : public ContextBase {
86 std::string deviceId;
87 std::string key;
88 JSUtil::KvStoreVariant value;
89 };
90 auto ctxt = std::make_shared<GetContext>();
91 auto input = [env, ctxt](size_t argc, napi_value* argv) {
92 // number 2 means: required 2 arguments, <deviceId> + <key>
93 ASSERT_BUSINESS_ERR(ctxt, argc >= 1, Status::INVALID_ARGUMENT,
94 "Parameter error:Mandatory parameters are left unspecified");
95 if (argc > 1) {
96 ctxt->status = JSUtil::GetValue(env, argv[0], ctxt->deviceId);
97 ASSERT_BUSINESS_ERR(ctxt, ctxt->status == napi_ok, Status::INVALID_ARGUMENT,
98 "Parameter error:parameter deviceId must be string and not allow empty");
99 }
100 int32_t pos = (argc == 1) ? 0 : 1;
101 ctxt->status = JSUtil::GetValue(env, argv[pos], ctxt->key);
102 ASSERT_BUSINESS_ERR(ctxt, ctxt->status == napi_ok, Status::INVALID_ARGUMENT,
103 "Parameter error:type of key must be string and not allow empty");
104 };
105 ctxt->GetCbInfo(env, info, input);
106 ASSERT_NULL(!ctxt->isThrowError, "DeviceGet exits");
107
108 auto execute = [ctxt]() {
109 std::string deviceKey = GetDeviceKey(ctxt->deviceId, ctxt->key);
110 OHOS::DistributedKv::Key key(deviceKey);
111 OHOS::DistributedKv::Value value;
112 auto kvStore = reinterpret_cast<JsDeviceKVStore*>(ctxt->native)->GetKvStorePtr();
113 ASSERT_STATUS(ctxt, "kvStore->result() failed!");
114 bool isSchemaStore = reinterpret_cast<JsDeviceKVStore*>(ctxt->native)->IsSchemaStore();
115 Status status = kvStore->Get(key, value);
116 ZLOGD("kvStore->Get return %{public}d", status);
117 ctxt->value = isSchemaStore ? value.ToString() : JSUtil::Blob2VariantValue(value);
118 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
119 napi_ok : napi_generic_failure;
120 };
121 auto output = [env, ctxt](napi_value& result) {
122 ctxt->status = JSUtil::SetValue(env, ctxt->value, result);
123 ASSERT_STATUS(ctxt, "output failed");
124 };
125 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
126 }
127
128 struct VariantArgs {
129 /* input arguments' combinations */
130 DataQuery dataQuery;
131 std::string errMsg = "";
132 };
133
GetVariantArgs(napi_env env,size_t argc,napi_value * argv,VariantArgs & va)134 static JSUtil::StatusMsg GetVariantArgs(napi_env env, size_t argc, napi_value* argv, VariantArgs& va)
135 {
136 int32_t pos = (argc == 1) ? 0 : 1;
137 napi_valuetype type = napi_undefined;
138 JSUtil::StatusMsg statusMsg = napi_typeof(env, argv[pos], &type);
139 if (statusMsg != napi_ok || (type != napi_string && type != napi_object)) {
140 va.errMsg = "Parameter error:parameters keyPrefix/query must be string or object";
141 return statusMsg != napi_ok ? statusMsg.status : napi_invalid_arg;
142 }
143 if (type == napi_string) {
144 std::string keyPrefix;
145 statusMsg = JSUtil::GetValue(env, argv[pos], keyPrefix);
146 if (keyPrefix.empty()) {
147 va.errMsg = "Parameter error:parameters keyPrefix must be string";
148 return napi_invalid_arg;
149 }
150 va.dataQuery.KeyPrefix(keyPrefix);
151 } else {
152 bool result = false;
153 statusMsg = napi_instanceof(env, argv[pos], JsQuery::Constructor(env), &result);
154 if ((statusMsg.status == napi_ok) && (result != false)) {
155 JsQuery *jsQuery = nullptr;
156 statusMsg = JSUtil::Unwrap(env, argv[pos], reinterpret_cast<void **>(&jsQuery), JsQuery::Constructor(env));
157 if (jsQuery == nullptr) {
158 va.errMsg = "Parameter error:The parameters query must be string";
159 return napi_invalid_arg;
160 }
161 va.dataQuery = jsQuery->GetDataQuery();
162 } else {
163 statusMsg = JSUtil::GetValue(env, argv[pos], va.dataQuery);
164 ZLOGD("kvStoreDataShare->GetResultSet return %{public}d", statusMsg.status);
165 statusMsg.jsApiType = JSUtil::DATASHARE;
166 }
167 }
168 std::string deviceId;
169 if (argc > 1) {
170 JSUtil::GetValue(env, argv[0], deviceId);
171 va.dataQuery.DeviceId(deviceId);
172 }
173 return statusMsg;
174 };
175
176 /*
177 * [JS API Prototype]
178 * getEntries(deviceId:string, keyPrefix:string, callback:AsyncCallback<Entry[]>):void
179 * getEntries(deviceId:string, keyPrefix:string):Promise<Entry[]>
180 *
181 * getEntries(query:Query, callback:AsyncCallback<Entry[]>):void
182 * getEntries(query:Query) : Promise<Entry[]>
183 *
184 * getEntries(deviceId:string, query:Query):callback:AsyncCallback<Entry[]>):void
185 * getEntries(deviceId:string, query:Query):Promise<Entry[]>
186 */
GetEntries(napi_env env,napi_callback_info info)187 napi_value JsDeviceKVStore::GetEntries(napi_env env, napi_callback_info info)
188 {
189 struct GetEntriesContext : public ContextBase {
190 VariantArgs va;
191 std::vector<Entry> entries;
192 };
193 auto ctxt = std::make_shared<GetEntriesContext>();
194 auto input = [env, ctxt](size_t argc, napi_value* argv) {
195 ASSERT_BUSINESS_ERR(ctxt, argc >= 1, Status::INVALID_ARGUMENT,
196 "Parameter error:Mandatory parameters are left unspecified");
197 ctxt->status = GetVariantArgs(env, argc, argv, ctxt->va);
198 ASSERT_BUSINESS_ERR(ctxt, ctxt->status == napi_ok, Status::INVALID_ARGUMENT, ctxt->va.errMsg);
199 };
200 ctxt->GetCbInfo(env, info, input);
201 ASSERT_NULL(!ctxt->isThrowError, "GetEntries exit");
202
203 auto execute = [ctxt]() {
204 auto kvStore = reinterpret_cast<JsDeviceKVStore*>(ctxt->native)->GetKvStorePtr();
205 Status status = kvStore->GetEntries(ctxt->va.dataQuery, ctxt->entries);
206 ZLOGD("kvStore->GetEntries() return %{public}d", status);
207 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
208 napi_ok : napi_generic_failure;
209 };
210 auto output = [env, ctxt](napi_value& result) {
211 auto isSchemaStore = reinterpret_cast<JsDeviceKVStore*>(ctxt->native)->IsSchemaStore();
212 ctxt->status = JSUtil::SetValue(env, ctxt->entries, result, isSchemaStore);
213 ASSERT_STATUS(ctxt, "output failed!");
214 };
215 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
216 }
217
218 /*
219 * [JS API Prototype]
220 * getResultSet(deviceId:string, keyPrefix:string, callback:AsyncCallback<KvStoreResultSet>):void
221 * getResultSet(deviceId:string, keyPrefix:string):Promise<KvStoreResultSet>
222 *
223 * getResultSet(query:Query, callback:AsyncCallback<KvStoreResultSet>):void
224 * getResultSet(query:Query):Promise<KvStoreResultSet>
225 *
226 * getResultSet(deviceId:string, query:Query, callback:AsyncCallback<KvStoreResultSet>):void
227 * getResultSet(deviceId:string, query:Query):Promise<KvStoreResultSet>
228 */
GetResultSet(napi_env env,napi_callback_info info)229 napi_value JsDeviceKVStore::GetResultSet(napi_env env, napi_callback_info info)
230 {
231 struct GetResultSetContext : public ContextBase {
232 VariantArgs va;
233 JsKVStoreResultSet* resultSet = nullptr;
234 napi_ref ref = nullptr;
235 };
236 auto ctxt = std::make_shared<GetResultSetContext>();
237 auto input = [env, ctxt](size_t argc, napi_value* argv) {
238 ASSERT_BUSINESS_ERR(ctxt, argc >= 1, Status::INVALID_ARGUMENT,
239 "Parameter error:Mandatory parameters are left unspecified");
240 JSUtil::StatusMsg statusMsg = GetVariantArgs(env, argc, argv, ctxt->va);
241 ctxt->status = statusMsg.status;
242 ASSERT_BUSINESS_ERR(ctxt, ctxt->status == napi_ok, Status::INVALID_ARGUMENT, ctxt->va.errMsg);
243 ASSERT_PERMISSION_ERR(ctxt,
244 !JSUtil::IsSystemApi(statusMsg.jsApiType) ||
245 reinterpret_cast<JsSingleKVStore *>(ctxt->native)->IsSystemApp(), Status::PERMISSION_DENIED, "");
246 ctxt->ref = JSUtil::NewWithRef(env, 0, nullptr, reinterpret_cast<void **>(&ctxt->resultSet),
247 JsKVStoreResultSet::Constructor(env));
248 ASSERT_BUSINESS_ERR(ctxt, ctxt->resultSet != nullptr, Status::INVALID_ARGUMENT,
249 "Parameter error:resultSet is null");
250 };
251 ctxt->GetCbInfo(env, info, input);
252 ASSERT_NULL(!ctxt->isThrowError, "GetResultSet exit");
253
254 auto execute = [ctxt]() {
255 std::shared_ptr<KvStoreResultSet> kvResultSet;
256 auto kvStore = reinterpret_cast<JsDeviceKVStore*>(ctxt->native)->GetKvStorePtr();
257 Status status = kvStore->GetResultSet(ctxt->va.dataQuery, kvResultSet);
258 ZLOGD("kvStore->GetResultSet() return %{public}d", status);
259 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
260 napi_ok : napi_generic_failure;
261 ctxt->resultSet->SetInstance(kvResultSet);
262 bool isSchema = reinterpret_cast<JsDeviceKVStore*>(ctxt->native)->IsSchemaStore();
263 ctxt->resultSet->SetSchema(isSchema);
264 };
265 auto output = [env, ctxt](napi_value& result) {
266 ctxt->status = napi_get_reference_value(env, ctxt->ref, &result);
267 napi_delete_reference(env, ctxt->ref);
268 ASSERT_STATUS(ctxt, "output KvResultSet failed");
269 };
270 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
271 }
272
273 /*
274 * [JS API Prototype]
275 * getResultSize(query:Query, callback: AsyncCallback<number>):void
276 * getResultSize(query:Query):Promise<number>
277 *
278 * getResultSize(deviceId:string, query:Query, callback: AsyncCallback<number>):void
279 * getResultSize(deviceId:string, query:Query):Promise<number>
280 */
GetResultSize(napi_env env,napi_callback_info info)281 napi_value JsDeviceKVStore::GetResultSize(napi_env env, napi_callback_info info)
282 {
283 struct ResultSizeContext : public ContextBase {
284 VariantArgs va;
285 int resultSize = 0;
286 };
287 auto ctxt = std::make_shared<ResultSizeContext>();
288 auto input = [env, ctxt](size_t argc, napi_value* argv) {
289 ASSERT_BUSINESS_ERR(ctxt, argc >= 1, Status::INVALID_ARGUMENT,
290 "Parameter error:Mandatory parameters are left unspecified");
291 ctxt->status = GetVariantArgs(env, argc, argv, ctxt->va);
292 ASSERT_BUSINESS_ERR(ctxt, ctxt->status != napi_invalid_arg, Status::INVALID_ARGUMENT, ctxt->va.errMsg);
293 };
294
295 ctxt->GetCbInfo(env, info, input);
296 ASSERT_NULL(!ctxt->isThrowError, "GetResultSize exit");
297
298 auto execute = [ctxt]() {
299 auto kvStore = reinterpret_cast<JsDeviceKVStore*>(ctxt->native)->GetKvStorePtr();
300 Status status = kvStore->GetCount(ctxt->va.dataQuery, ctxt->resultSize);
301 ZLOGD("kvStore->GetCount() return %{public}d", status);
302 ctxt->status = (GenerateNapiError(status, ctxt->jsCode, ctxt->error) == Status::SUCCESS) ?
303 napi_ok : napi_generic_failure;
304 };
305 auto output = [env, ctxt](napi_value& result) {
306 ctxt->status = JSUtil::SetValue(env, static_cast<int32_t>(ctxt->resultSize), result);
307 ASSERT_STATUS(ctxt, "output resultSize failed!");
308 };
309 return NapiQueue::AsyncWork(env, ctxt, std::string(__FUNCTION__), execute, output);
310 }
311
New(napi_env env,napi_callback_info info)312 napi_value JsDeviceKVStore::New(napi_env env, napi_callback_info info)
313 {
314 ZLOGD("Constructor single kv store!");
315 std::string storeId;
316 auto ctxt = std::make_shared<ContextBase>();
317 auto input = [env, ctxt, &storeId](size_t argc, napi_value* argv) {
318 // required 2 arguments :: <storeId> <options>
319 ASSERT_BUSINESS_ERR(ctxt, argc >= 2, Status::INVALID_ARGUMENT,
320 "Parameter error:Mandatory parameters are left unspecified");
321 ctxt->status = JSUtil::GetValue(env, argv[0], storeId);
322 ASSERT_BUSINESS_ERR(ctxt, (ctxt->status == napi_ok) && !storeId.empty(), Status::INVALID_ARGUMENT,
323 "The type of storeId must be string.");
324 };
325 ctxt->GetCbInfoSync(env, info, input);
326 ASSERT_NULL(!ctxt->isThrowError, "New JsDeviceKVStore exit");
327
328 JsDeviceKVStore* kvStore = new (std::nothrow) JsDeviceKVStore(storeId);
329 ASSERT_ERR(env, kvStore != nullptr, Status::INVALID_ARGUMENT, "Parameter error:kvStore is nullptr");
330
331 auto finalize = [](napi_env env, void* data, void* hint) {
332 ZLOGI("deviceKvStore finalize.");
333 auto* kvStore = reinterpret_cast<JsDeviceKVStore*>(data);
334 ASSERT_VOID(kvStore != nullptr, "kvStore is null!");
335 delete kvStore;
336 };
337 ASSERT_CALL(env, napi_wrap(env, ctxt->self, kvStore, finalize, nullptr, nullptr), kvStore);
338 return ctxt->self;
339 }
340 } // namespace OHOS::DistributedKVStore
341