• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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