1 /*
2 * Copyright (c) 2025 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
16 #include "cloud_file_version_napi.h"
17
18 #include <sys/types.h>
19
20 #include "async_work.h"
21 #include "cloud_sync_manager.h"
22 #include "dfs_error.h"
23 #include "utils_log.h"
24 #include "uv.h"
25
26 namespace OHOS::FileManagement::CloudSync {
27 using namespace FileManagement::LibN;
28 using namespace std;
29
30 static const int64_t PERCENT = 100;
31 static const int32_t TOP_NUM = 10000;
32 static const int DEC = 10;
33 const int32_t ARGS_ONE = 1;
34
35 struct VersionParams {
36 vector<HistoryVersion> versionList;
37 string versionUri;
38 bool isConflict = false;
39 int64_t downloadId = 0;
40 };
41
Constructor(napi_env env,napi_callback_info info)42 napi_value FileVersionNapi::Constructor(napi_env env, napi_callback_info info)
43 {
44 NFuncArg funcArg(env, info);
45 if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
46 LOGE("Start Number of arguments unmatched");
47 NError(EINVAL).ThrowErr(env);
48 return nullptr;
49 }
50 auto fileVersionEntity = make_unique<FileVersionEntity>();
51 if (!NClass::SetEntityFor<FileVersionEntity>(env, funcArg.GetThisVar(), move(fileVersionEntity))) {
52 LOGE("Failed to set file cache entity.");
53 NError(EINVAL).ThrowErr(env);
54 return nullptr;
55 }
56 return funcArg.GetThisVar();
57 }
58
AsyncComplete(const napi_env & env,const vector<HistoryVersion> & list)59 static NVal AsyncComplete(const napi_env &env, const vector<HistoryVersion> &list)
60 {
61 napi_value results = nullptr;
62
63 napi_status status = napi_create_array(env, &results);
64 if (status != napi_ok) {
65 LOGE("Failed to create array");
66 return {env, NError(LibN::STORAGE_SERVICE_SYS_CAP_TAG + LibN::E_IPCSS).GetNapiErr(env)};
67 }
68
69 int32_t index = 0;
70 for (auto version : list) {
71 NVal obj = NVal::CreateObject(env);
72 obj.AddProp("editedTime", NVal::CreateInt64(env, version.editedTime).val_);
73 obj.AddProp("fileSize", NVal::CreateBigIntUint64(env, version.fileSize).val_);
74 obj.AddProp("versionId", NVal::CreateUTF8String(env, to_string(version.versionId)).val_);
75 obj.AddProp("originalFileName", NVal::CreateUTF8String(env, version.originalFileName).val_);
76 obj.AddProp("sha256", NVal::CreateUTF8String(env, version.sha256).val_);
77 obj.AddProp("autoResolved", NVal::CreateBool(env, version.resolved).val_);
78 status = napi_set_element(env, results, index, obj.val_);
79 if (status != napi_ok) {
80 LOGE("Failed to set element on data");
81 return {env, NError(LibN::STORAGE_SERVICE_SYS_CAP_TAG + LibN::E_IPCSS).GetNapiErr(env)};
82 }
83 index++;
84 }
85 return {env, results};
86 }
87
GetHistoryVersionListParam(const napi_env & env,const NFuncArg & funcArg)88 static tuple<bool, string, int32_t> GetHistoryVersionListParam(const napi_env &env, const NFuncArg &funcArg)
89 {
90 auto [succ, uriNVal, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
91 if (!succ) {
92 LOGE("unavailable uri.");
93 return {false, "", 0};
94 }
95 string uri = uriNVal.get();
96 if (uri.empty()) {
97 LOGE("unavailable uri.");
98 return {false, "", 0};
99 }
100 auto [succLimit, numLimit] = NVal(env, funcArg[NARG_POS::SECOND]).ToInt32();
101 if (!succLimit || numLimit <= 0) {
102 LOGE("unavailable version num limit.");
103 return {false, "", 0};
104 }
105
106 return make_tuple(true, move(uri), numLimit >= TOP_NUM ? TOP_NUM : numLimit);
107 }
108
GetHistoryVersionList(napi_env env,napi_callback_info info)109 napi_value FileVersionNapi::GetHistoryVersionList(napi_env env, napi_callback_info info)
110 {
111 NFuncArg funcArg(env, info);
112 if (!funcArg.InitArgs(NARG_CNT::TWO)) {
113 LOGE("Start Number of arguments unmatched");
114 NError(EINVAL).ThrowErr(env);
115 return nullptr;
116 }
117
118 auto [succ, uriNVal, numNVal] = GetHistoryVersionListParam(env, funcArg);
119 if (!succ) {
120 LOGE("param is unavailable.");
121 NError(EINVAL).ThrowErr(env);
122 return nullptr;
123 }
124
125 auto param = make_shared<VersionParams>();
126 auto cbExec = [uri{uriNVal}, num{numNVal}, param]() -> NError {
127 int32_t ret = CloudSyncManager::GetInstance().GetHistoryVersionList(uri, num, param->versionList);
128 if (ret != E_OK) {
129 LOGE("get history version list faild, ret = %{public}d", ret);
130 return NError(Convert2JsErrNum(ret));
131 }
132 return NError(NO_ERROR);
133 };
134
135 auto cbCompl = [param](napi_env env, NError err) -> NVal {
136 if (err) {
137 return {env, err.GetNapiErr(env)};
138 }
139 return AsyncComplete(env, param->versionList);
140 };
141
142 string procedureName = "fileVersion";
143 auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, static_cast<size_t>(NARG_CNT::THREE));
144 return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
145 }
146
SafeStringToUInt(const string & str,uint64_t & value)147 static bool SafeStringToUInt(const string &str, uint64_t &value)
148 {
149 char *endPtr = nullptr;
150 errno = 0;
151
152 value = strtoull(str.c_str(), &endPtr, DEC);
153 if (str.c_str() == endPtr || *endPtr != '\0' || errno == ERANGE) {
154 LOGE("string to uint64 failed.");
155 return false;
156 }
157
158 return true;
159 }
160
GetDownloadVersionParam(const napi_env & env,const NFuncArg & funcArg)161 static tuple<bool, string, uint64_t, NVal> GetDownloadVersionParam(const napi_env &env, const NFuncArg &funcArg)
162 {
163 NVal res;
164 auto [succ, uriNVal, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
165 if (!succ) {
166 LOGE("unavailable uri.");
167 return {false, "", 0, res};
168 }
169 string uri = uriNVal.get();
170 if (uri.empty()) {
171 LOGE("unavailable uri.");
172 return {false, "", 0, res};
173 }
174 auto [succId, versionNVal, ignoreId] = NVal(env, funcArg[NARG_POS::SECOND]).ToUTF8String();
175 if (!succId) {
176 LOGE("unavailable version id.");
177 return {false, "", 0, res};
178 }
179 string versionId = versionNVal.get();
180 if (versionId.empty()) {
181 LOGE("unavailable version id.");
182 return {false, "", 0, res};
183 }
184 bool isDigit = std::all_of(versionId.begin(), versionId.end(), ::isdigit);
185 if (!isDigit) {
186 LOGE("unavailable version id.");
187 return {false, "", 0, res};
188 }
189 uint64_t value = 0;
190 if (!SafeStringToUInt(versionId, value)) {
191 LOGE("unavailable version id.");
192 return {false, "", 0, res};
193 }
194
195 NVal callbackVal(env, funcArg[NARG_POS::THIRD]);
196 if (!callbackVal.TypeIs(napi_function)) {
197 LOGE("download version argument type mismatch");
198 return {false, "", 0, res};
199 }
200
201 return make_tuple(true, move(uri), value, move(callbackVal));
202 }
203
DownloadHistoryVersion(napi_env env,napi_callback_info info)204 napi_value FileVersionNapi::DownloadHistoryVersion(napi_env env, napi_callback_info info)
205 {
206 NFuncArg funcArg(env, info);
207 if (!funcArg.InitArgs(NARG_CNT::THREE)) {
208 LOGE("Start Number of arguments unmatched");
209 NError(EINVAL).ThrowErr(env);
210 return nullptr;
211 }
212 auto [succ, uriNVal, idNVal, callbackRef] = GetDownloadVersionParam(env, funcArg);
213 if (!succ) {
214 LOGE("param is unavailable.");
215 NError(EINVAL).ThrowErr(env);
216 return nullptr;
217 }
218 auto fileVersionEntity = NClass::GetEntityOf<FileVersionEntity>(env, funcArg.GetThisVar());
219 if (!fileVersionEntity) {
220 LOGE("Failed to get file version entity.");
221 NError(Convert2JsErrNum(E_SERVICE_INNER_ERROR)).ThrowErr(env);
222 return nullptr;
223 }
224 {
225 lock_guard<mutex> lock(mtx_);
226 if (fileVersionEntity->callbackImpl == nullptr) {
227 fileVersionEntity->callbackImpl = make_shared<VersionDownloadCallbackImpl>(env, callbackRef.val_);
228 }
229 }
230 auto param = make_shared<VersionParams>();
231 auto cbExec = [uri{uriNVal}, id{idNVal}, callback{fileVersionEntity->callbackImpl}, param]() -> NError {
232 if (!callback) {
233 LOGE("download history version callback is null.");
234 return NError(Convert2JsErrNum(E_SERVICE_INNER_ERROR));
235 }
236 int32_t ret = CloudSyncManager::GetInstance()
237 .DownloadHistoryVersion(uri, param->downloadId, id, callback, param->versionUri);
238 if (ret != E_OK) {
239 LOGE("Start download history version failed! ret = %{public}d", ret);
240 return NError(Convert2JsErrNum(ret));
241 }
242 return NError(NO_ERROR);
243 };
244 auto cbCompl = [param](napi_env env, NError err) -> NVal {
245 if (err) {
246 return {env, err.GetNapiErr(env)};
247 }
248 return NVal::CreateUTF8String(env, param->versionUri);
249 };
250 string procedureName = "fileVersion";
251 auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, static_cast<size_t>(NARG_CNT::FOUR));
252 return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
253 }
254
GetReplaceVersionListParam(const napi_env & env,const NFuncArg & funcArg)255 static tuple<bool, string, string> GetReplaceVersionListParam(const napi_env &env, const NFuncArg &funcArg)
256 {
257 auto [succOri, uriOriNVal, ignoreOri] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
258 if (!succOri) {
259 LOGE("unavailable origin uri.");
260 return {false, "", ""};
261 }
262 string uriOri = uriOriNVal.get();
263 if (uriOri.empty()) {
264 LOGE("unavailable origin uri.");
265 return {false, "", ""};
266 }
267 auto [succUri, uriNVal, ignore] = NVal(env, funcArg[NARG_POS::SECOND]).ToUTF8String();
268 if (!succUri) {
269 LOGE("unavailable uri.");
270 return {false, "", ""};
271 }
272 string uri = uriNVal.get();
273 if (uri.empty()) {
274 LOGE("unavailable uri.");
275 return {false, "", ""};
276 }
277
278 return make_tuple(true, move(uriOri), move(uri));
279 }
280
ReplaceFileWithHistoryVersion(napi_env env,napi_callback_info info)281 napi_value FileVersionNapi::ReplaceFileWithHistoryVersion(napi_env env, napi_callback_info info)
282 {
283 NFuncArg funcArg(env, info);
284 if (!funcArg.InitArgs(NARG_CNT::TWO)) {
285 LOGE("Start Number of arguments unmatched");
286 NError(EINVAL).ThrowErr(env);
287 return nullptr;
288 }
289
290 auto [succ, oriUriNVal, uriNVal] = GetReplaceVersionListParam(env, funcArg);
291 if (!succ) {
292 LOGE("param is unavailable.");
293 NError(EINVAL).ThrowErr(env);
294 return nullptr;
295 }
296
297 auto cbExec = [oriUri{oriUriNVal}, uri{uriNVal}]() -> NError {
298 int32_t ret = CloudSyncManager::GetInstance().ReplaceFileWithHistoryVersion(oriUri, uri);
299 if (ret != E_OK) {
300 LOGE("replace history version failed! ret = %{public}d", ret);
301 return NError(Convert2JsErrNum(ret));
302 }
303 return NError(NO_ERROR);
304 };
305
306 auto cbCompl = [](napi_env env, NError err) -> NVal {
307 if (err) {
308 return {env, err.GetNapiErr(env)};
309 }
310 return NVal::CreateUndefined(env);
311 };
312
313 string procedureName = "fileVersion";
314 auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, static_cast<size_t>(NARG_CNT::THREE));
315 return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
316 }
317
IsConflict(napi_env env,napi_callback_info info)318 napi_value FileVersionNapi::IsConflict(napi_env env, napi_callback_info info)
319 {
320 NFuncArg funcArg(env, info);
321 if (!funcArg.InitArgs(NARG_CNT::ONE)) {
322 LOGE("Start Number of arguments unmatched");
323 NError(EINVAL).ThrowErr(env);
324 return nullptr;
325 }
326
327 auto [succ, uriNVal, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
328 if (!succ) {
329 LOGE("unavailable uri.");
330 NError(EINVAL).ThrowErr(env);
331 return nullptr;
332 }
333 string uri = uriNVal.get();
334 if (uri.empty()) {
335 LOGE("unavailable uri.");
336 NError(EINVAL).ThrowErr(env);
337 return nullptr;
338 }
339
340 auto param = make_shared<VersionParams>();
341 auto cbExec = [uriIn{uri}, param]() -> NError {
342 int32_t ret = CloudSyncManager::GetInstance().IsFileConflict(uriIn, param->isConflict);
343 if (ret != E_OK) {
344 LOGE("replace history version failed! ret = %{public}d", ret);
345 return NError(Convert2JsErrNum(ret));
346 }
347 return NError(NO_ERROR);
348 };
349
350 auto cbCompl = [param](napi_env env, NError err) -> NVal {
351 if (err) {
352 return {env, err.GetNapiErr(env)};
353 }
354 return NVal::CreateBool(env, param->isConflict);
355 };
356
357 string procedureName = "fileVersion";
358 auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, static_cast<size_t>(NARG_CNT::TWO));
359 return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
360 }
361
ClearFileConflict(napi_env env,napi_callback_info info)362 napi_value FileVersionNapi::ClearFileConflict(napi_env env, napi_callback_info info)
363 {
364 NFuncArg funcArg(env, info);
365 if (!funcArg.InitArgs(NARG_CNT::ONE)) {
366 LOGE("Start Number of arguments unmatched");
367 NError(EINVAL).ThrowErr(env);
368 return nullptr;
369 }
370
371 auto [succ, uriNVal, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
372 if (!succ) {
373 LOGE("unavailable uri.");
374 NError(EINVAL).ThrowErr(env);
375 return nullptr;
376 }
377 string uri = uriNVal.get();
378 if (uri.empty()) {
379 LOGE("unavailable uri.");
380 NError(EINVAL).ThrowErr(env);
381 return nullptr;
382 }
383
384 auto cbExec = [uriIn{uri}]() -> NError {
385 int32_t ret = CloudSyncManager::GetInstance().ClearFileConflict(uriIn);
386 if (ret != E_OK) {
387 LOGE("replace history version failed! ret = %{public}d", ret);
388 return NError(Convert2JsErrNum(ret));
389 }
390 return NError(NO_ERROR);
391 };
392
393 auto cbCompl = [](napi_env env, NError err) -> NVal {
394 if (err) {
395 return {env, err.GetNapiErr(env)};
396 }
397 return NVal::CreateUndefined(env);
398 };
399
400 string procedureName = "fileVersion";
401 auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, static_cast<size_t>(NARG_CNT::TWO));
402 return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
403 }
404
Export()405 bool FileVersionNapi::Export()
406 {
407 std::vector<napi_property_descriptor> props = {
408 NVal::DeclareNapiFunction("getHistoryVersionList", GetHistoryVersionList),
409 NVal::DeclareNapiFunction("downloadHistoryVersion", DownloadHistoryVersion),
410 NVal::DeclareNapiFunction("replaceFileWithHistoryVersion", ReplaceFileWithHistoryVersion),
411 NVal::DeclareNapiFunction("isFileConflict", IsConflict),
412 NVal::DeclareNapiFunction("clearFileConflict", ClearFileConflict),
413 };
414 std::string className = GetClassName();
415 auto [succ, classValue] =
416 NClass::DefineClass(exports_.env_, className, FileVersionNapi::Constructor, std::move(props));
417 if (!succ) {
418 NError(E_GETRESULT).ThrowErr(exports_.env_);
419 LOGE("Failed to define FileVersion class");
420 return false;
421 }
422
423 succ = NClass::SaveClass(exports_.env_, className, classValue);
424 if (!succ) {
425 NError(E_GETRESULT).ThrowErr(exports_.env_);
426 LOGE("Failed to save FileVersion class");
427 return false;
428 }
429
430 return exports_.AddProp(className, classValue);
431 }
432
GetClassName()433 string FileVersionNapi::GetClassName()
434 {
435 return className_;
436 }
437
VersionDownloadCallbackImpl(napi_env env,napi_value fun)438 VersionDownloadCallbackImpl::VersionDownloadCallbackImpl(napi_env env, napi_value fun) : env_(env)
439 {
440 if (fun != nullptr) {
441 napi_create_reference(env_, fun, 1, &cbOnRef_);
442 }
443 }
444
OnComplete(UvChangeMsg * msg)445 void VersionDownloadCallbackImpl::OnComplete(UvChangeMsg *msg)
446 {
447 auto downloadcCallback = msg->downloadCallback_.lock();
448 if (downloadcCallback == nullptr || downloadcCallback->cbOnRef_ == nullptr) {
449 LOGE("downloadcCallback->cbOnRef_ is nullptr");
450 return;
451 }
452 auto env = downloadcCallback->env_;
453 auto ref = downloadcCallback->cbOnRef_;
454 napi_handle_scope scope = nullptr;
455 napi_open_handle_scope(env, &scope);
456 napi_value jsCallback = nullptr;
457 napi_status status = napi_get_reference_value(env, ref, &jsCallback);
458 if (status != napi_ok) {
459 LOGE("Create reference failed, status: %{public}d", status);
460 napi_close_handle_scope(env, scope);
461 return;
462 }
463 NVal obj = NVal::CreateObject(env);
464 auto process = (msg->downloadProgress_.downloadedSize * PERCENT) / msg->downloadProgress_.totalSize;
465 obj.AddProp("state", NVal::CreateInt32(env, (int32_t)msg->downloadProgress_.state).val_);
466 obj.AddProp("progress", NVal::CreateInt64(env, process).val_);
467 obj.AddProp("taskId", NVal::CreateInt64(env, msg->downloadProgress_.downloadId).val_);
468 obj.AddProp("errType", NVal::CreateInt32(env, (int32_t)msg->downloadProgress_.downloadErrorType).val_);
469
470 LOGI("OnComplete callback start for taskId: %{public}lld",
471 static_cast<long long>(msg->downloadProgress_.downloadId));
472 napi_value retVal = nullptr;
473 napi_value global = nullptr;
474 napi_get_global(env, &global);
475 status = napi_call_function(env, global, jsCallback, ARGS_ONE, &(obj.val_), &retVal);
476 if (status != napi_ok) {
477 LOGE("napi call function failed, status: %{public}d", status);
478 }
479 napi_close_handle_scope(env, scope);
480 }
481
OnDownloadProcess(const DownloadProgressObj & progress)482 void VersionDownloadCallbackImpl::OnDownloadProcess(const DownloadProgressObj &progress)
483 {
484 UvChangeMsg *msg = new (std::nothrow) UvChangeMsg(shared_from_this(), progress);
485 if (msg == nullptr) {
486 LOGE("Failed to create uv message object");
487 return;
488 }
489 auto task = [msg]() {
490 if (msg->downloadCallback_.expired()) {
491 LOGE("downloadCallback_ is expired");
492 delete msg;
493 return;
494 }
495 msg->downloadCallback_.lock()->OnComplete(msg);
496 delete msg;
497 };
498 napi_status ret = napi_send_event(env_, task, napi_event_priority::napi_eprio_immediate);
499 if (ret != napi_ok) {
500 LOGE("Failed to execute libuv work queue, ret: %{public}d", ret);
501 delete msg;
502 return;
503 }
504 }
505
~VersionDownloadCallbackImpl()506 VersionDownloadCallbackImpl::~VersionDownloadCallbackImpl()
507 {
508 if (cbOnRef_ != nullptr) {
509 napi_status status = napi_delete_reference(env_, cbOnRef_);
510 if (status != napi_ok) {
511 LOGE("Failed to delete napi ref, %{public}d", status);
512 }
513 cbOnRef_ = nullptr;
514 }
515 }
516 } // namespace OHOS::FileManagement::CloudSync