1 /*
2  * Copyright (c) 2023 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_download_napi.h"
17 
18 #include <sys/types.h>
19 
20 #include "async_work.h"
21 #include "cloud_file_napi.h"
22 #include "cloud_sync_manager.h"
23 #include "dfs_error.h"
24 #include "utils_log.h"
25 #include "uv.h"
26 
27 namespace OHOS::FileManagement::CloudSync {
28 using namespace FileManagement::LibN;
29 using namespace std;
30 const int32_t ARGS_ONE = 1;
31 
Constructor(napi_env env,napi_callback_info info)32 napi_value CloudFileNapi::Constructor(napi_env env, napi_callback_info info)
33 {
34     LOGI("CloudFileNapi::Constructor begin");
35     NFuncArg funcArg(env, info);
36     if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
37         LOGE("Start Number of arguments unmatched");
38         NError(E_PARAMS).ThrowErr(env);
39         return nullptr;
40     }
41 
42     LOGI("CloudFileNapi::Constructor end");
43     return funcArg.GetThisVar();
44 }
45 
Start(napi_env env,napi_callback_info info)46 napi_value CloudFileNapi::Start(napi_env env, napi_callback_info info)
47 {
48     LOGI("Start begin");
49     NFuncArg funcArg(env, info);
50     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
51         LOGE("Start Number of arguments unmatched");
52         NError(E_PARAMS).ThrowErr(env);
53         return nullptr;
54     }
55     auto [succUri, uri, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
56     if (!succUri) {
57         LOGE("Start get uri parameter failed!");
58         NError(E_PARAMS).ThrowErr(env);
59         return nullptr;
60     }
61 
62     auto cbExec = [uri = string(uri.get()), env = env]() -> NError {
63         int32_t ret = CloudSyncManager::GetInstance().StartDownloadFile(uri);
64         if (ret != E_OK) {
65             LOGE("Start Download failed! ret = %{public}d", ret);
66             return NError(Convert2JsErrNum(ret));
67         }
68         LOGI("Start Download Success!");
69         return NError(ERRNO_NOERR);
70     };
71 
72     auto cbCompl = [](napi_env env, NError err) -> NVal {
73         if (err) {
74             return {env, err.GetNapiErr(env)};
75         }
76         return NVal::CreateUndefined(env);
77     };
78 
79     string procedureName = "cloudFileDownload";
80     auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, static_cast<size_t>(NARG_CNT::TWO));
81     return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
82 }
83 
CloudDownloadCallbackImpl(napi_env env,napi_value fun,bool isBatch)84 CloudDownloadCallbackImpl::CloudDownloadCallbackImpl(napi_env env, napi_value fun, bool isBatch) : env_(env)
85 {
86     if (fun != nullptr) {
87         napi_create_reference(env_, fun, 1, &cbOnRef_);
88     }
89     isBatch_ = isBatch;
90 }
91 
OnComplete(UvChangeMsg * msg)92 void CloudDownloadCallbackImpl::OnComplete(UvChangeMsg *msg)
93 {
94     auto downloadcCallback = msg->CloudDownloadCallback_.lock();
95     if (downloadcCallback == nullptr || downloadcCallback->cbOnRef_ == nullptr) {
96         LOGE("downloadcCallback->cbOnRef_ is nullptr");
97         return;
98     }
99     auto env = downloadcCallback->env_;
100     auto ref = downloadcCallback->cbOnRef_;
101     napi_handle_scope scope = nullptr;
102     napi_open_handle_scope(env, &scope);
103     napi_value jsCallback = nullptr;
104     napi_status status = napi_get_reference_value(env, ref, &jsCallback);
105     if (status != napi_ok) {
106         LOGE("Create reference failed, status: %{public}d", status);
107         napi_close_handle_scope(env, scope);
108         return;
109     }
110     NVal obj = NVal::CreateObject(env);
111     if (!msg->isBatch_) {
112         obj.AddProp("state", NVal::CreateInt32(env, (int32_t)msg->downloadProgress_.state).val_);
113         obj.AddProp("processed", NVal::CreateInt64(env, msg->downloadProgress_.downloadedSize).val_);
114         obj.AddProp("size", NVal::CreateInt64(env, msg->downloadProgress_.totalSize).val_);
115         obj.AddProp("uri", NVal::CreateUTF8String(env, msg->downloadProgress_.path).val_);
116     } else {
117         LOGI("Batch download callback items");
118         obj.AddProp("state", NVal::CreateInt32(env, (int32_t)msg->downloadProgress_.batchState).val_);
119         obj.AddProp("downloadedSize", NVal::CreateInt64(env, msg->downloadProgress_.batchDownloadSize).val_);
120         obj.AddProp("totalSize", NVal::CreateInt64(env, msg->downloadProgress_.batchTotalSize).val_);
121         obj.AddProp("successfulNum", NVal::CreateInt64(env, msg->downloadProgress_.batchSuccNum).val_);
122         obj.AddProp("failedNum", NVal::CreateInt64(env, msg->downloadProgress_.batchFailNum).val_);
123         obj.AddProp("totalNum", NVal::CreateInt64(env, msg->downloadProgress_.batchTotalNum).val_);
124     }
125     obj.AddProp("taskId", NVal::CreateInt64(env, msg->downloadProgress_.downloadId).val_);
126     obj.AddProp("error", NVal::CreateInt32(env, (int32_t)msg->downloadProgress_.downloadErrorType).val_);
127 
128     napi_value retVal = nullptr;
129     napi_value global = nullptr;
130     napi_get_global(env, &global);
131     status = napi_call_function(env, global, jsCallback, ARGS_ONE, &(obj.val_), &retVal);
132     if (status != napi_ok) {
133         LOGE("napi call function failed, status: %{public}d", status);
134     }
135     napi_close_handle_scope(env, scope);
136 }
137 
OnDownloadProcess(const DownloadProgressObj & progress)138 void CloudDownloadCallbackImpl::OnDownloadProcess(const DownloadProgressObj &progress)
139 {
140     uv_loop_s *loop = nullptr;
141     napi_get_uv_event_loop(env_, &loop);
142     if (loop == nullptr) {
143         return;
144     }
145 
146     uv_work_t *work = new (nothrow) uv_work_t;
147     if (work == nullptr) {
148         LOGE("Failed to create uv work");
149         return;
150     }
151 
152     UvChangeMsg *msg = new (std::nothrow) UvChangeMsg(shared_from_this(), progress, isBatch_);
153     if (msg == nullptr) {
154         delete work;
155         return;
156     }
157     work->data = reinterpret_cast<void *>(msg);
158     int ret = uv_queue_work(
159         loop, work, [](uv_work_t *work) {},
160         [](uv_work_t *work, int status) {
161             auto msg = reinterpret_cast<UvChangeMsg *>(work->data);
162             OnComplete(msg);
163             delete msg;
164             delete work;
165         });
166     if (ret != 0) {
167         LOGE("Failed to execute libuv work queue, ret: %{public}d", ret);
168         delete msg;
169         delete work;
170     }
171 }
172 
DeleteReference()173 void CloudDownloadCallbackImpl::DeleteReference()
174 {
175     if (cbOnRef_ != nullptr) {
176         napi_delete_reference(env_, cbOnRef_);
177         cbOnRef_ = nullptr;
178     }
179 }
180 
On(napi_env env,napi_callback_info info)181 napi_value CloudFileNapi::On(napi_env env, napi_callback_info info)
182 {
183     LOGI("On begin");
184     NFuncArg funcArg(env, info);
185     if (!funcArg.InitArgs(NARG_CNT::TWO)) {
186         LOGE("On Number of arguments unmatched");
187         NError(E_PARAMS).ThrowErr(env);
188         return nullptr;
189     }
190     auto [succProgress, progress, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
191     if (!succProgress || std::string(progress.get()) != "progress") {
192         LOGE("On get progress failed!");
193         NError(E_PARAMS).ThrowErr(env);
194         return nullptr;
195     }
196 
197     if (!NVal(env, funcArg[NARG_POS::SECOND]).TypeIs(napi_function)) {
198         LOGE("Argument type mismatch");
199         NError(E_PARAMS).ThrowErr(env);
200         return nullptr;
201     }
202 
203     if (callback_ != nullptr) {
204         LOGI("callback already exist");
205         return NVal::CreateUndefined(env).val_;
206     }
207 
208     callback_ = make_shared<CloudDownloadCallbackImpl>(env, NVal(env, funcArg[(int)NARG_POS::SECOND]).val_);
209     int32_t ret = CloudSyncManager::GetInstance().RegisterDownloadFileCallback(callback_);
210     if (ret != E_OK) {
211         LOGE("RegisterDownloadFileCallback error, ret: %{public}d", ret);
212         NError(Convert2JsErrNum(ret)).ThrowErr(env);
213         return nullptr;
214     }
215 
216     return NVal::CreateUndefined(env).val_;
217 }
218 
Off(napi_env env,napi_callback_info info)219 napi_value CloudFileNapi::Off(napi_env env, napi_callback_info info)
220 {
221     LOGI("Off begin");
222     NFuncArg funcArg(env, info);
223     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
224         LOGE("Off Number of arguments unmatched");
225         NError(E_PARAMS).ThrowErr(env);
226         return nullptr;
227     }
228     auto [succProgress, progress, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
229     if (!succProgress || std::string(progress.get()) != "progress") {
230         LOGE("Off get progress failed!");
231         NError(E_PARAMS).ThrowErr(env);
232         return nullptr;
233     }
234 
235     if (funcArg.GetArgc() == (uint)NARG_CNT::TWO && !NVal(env, funcArg[NARG_POS::SECOND]).TypeIs(napi_function)) {
236         LOGE("Argument type mismatch");
237         NError(E_PARAMS).ThrowErr(env);
238         return nullptr;
239     }
240 
241     /* callback_ may be nullptr */
242     int32_t ret = CloudSyncManager::GetInstance().UnregisterDownloadFileCallback();
243     if (ret != E_OK) {
244         LOGE("UnregisterDownloadFileCallback error, ret: %{public}d", ret);
245         NError(Convert2JsErrNum(ret)).ThrowErr(env);
246         return nullptr;
247     }
248     if (callback_ != nullptr) {
249         /* napi delete reference */
250         callback_->DeleteReference();
251         callback_ = nullptr;
252     }
253     return NVal::CreateUndefined(env).val_;
254 }
255 
Stop(napi_env env,napi_callback_info info)256 napi_value CloudFileNapi::Stop(napi_env env, napi_callback_info info)
257 {
258     LOGI("Stop begin");
259     NFuncArg funcArg(env, info);
260     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::THREE)) {
261         LOGE("Stop Number of arguments unmatched");
262         NError(E_PARAMS).ThrowErr(env);
263         return nullptr;
264     }
265     auto [succUri, uri, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
266     if (!succUri) {
267         LOGE("Stop get uri parameter failed!");
268         NError(E_PARAMS).ThrowErr(env);
269         return nullptr;
270     }
271     bool needClean = false;
272     bool needCleanIgnore;
273     size_t maxArgSize = static_cast<size_t>(NARG_CNT::TWO);
274     if (funcArg.GetArgc() >= NARG_CNT::TWO) {
275         NVal ui(env, NVal(env, funcArg[(int)NARG_POS::SECOND]).val_);
276         if (ui.TypeIs(napi_boolean)) {
277             std::tie(needCleanIgnore, needClean) = NVal(env, funcArg[NARG_POS::SECOND]).ToBool();
278             maxArgSize = static_cast<size_t>(NARG_CNT::THREE);
279         }
280     }
281     if (funcArg.GetArgc() == NARG_CNT::THREE) {
282         maxArgSize = static_cast<size_t>(NARG_CNT::THREE);
283     }
284     auto cbExec = [uri = string(uri.get()), env = env, needClean = needClean]() -> NError {
285         int32_t ret = CloudSyncManager::GetInstance().StopDownloadFile(uri, needClean);
286         if (ret != E_OK) {
287             LOGE("Stop Download failed! ret = %{public}d", ret);
288             return NError(Convert2JsErrNum(ret));
289         }
290         LOGI("Stop Download Success!");
291         return NError(ERRNO_NOERR);
292     };
293 
294     auto cbCompl = [](napi_env env, NError err) -> NVal {
295         if (err) {
296             return {env, err.GetNapiErr(env)};
297         }
298         return NVal::CreateUndefined(env);
299     };
300 
301     string procedureName = "cloudFileDownload";
302     auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, maxArgSize);
303     return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
304 }
305 
ToExport(std::vector<napi_property_descriptor> props)306 bool CloudFileNapi::ToExport(std::vector<napi_property_descriptor> props)
307 {
308     std::string className = GetClassName();
309     auto [succ, classValue] =
310         NClass::DefineClass(exports_.env_, className, Constructor, std::move(props));
311     if (!succ) {
312         NError(E_GETRESULT).ThrowErr(exports_.env_);
313         LOGE("Failed to define GallerySync class");
314         return false;
315     }
316 
317     succ = NClass::SaveClass(exports_.env_, className, classValue);
318     if (!succ) {
319         NError(E_GETRESULT).ThrowErr(exports_.env_);
320         LOGE("Failed to save GallerySync class");
321         return false;
322     }
323 
324     return exports_.AddProp(className, classValue);
325 }
326 
Export()327 bool CloudFileNapi::Export()
328 {
329     vector<napi_property_descriptor> props = {
330         NVal::DeclareNapiFunction("start", CloudFileNapi::Start),
331         NVal::DeclareNapiFunction("on", CloudFileNapi::On),
332         NVal::DeclareNapiFunction("off", CloudFileNapi::Off),
333         NVal::DeclareNapiFunction("stop", CloudFileNapi::Stop),
334     };
335     return ToExport(props);
336 }
337 
SetClassName(std::string classname)338 void CloudFileNapi::SetClassName(std::string classname)
339 {
340     className_ = classname;
341 }
342 
GetClassName()343 string CloudFileNapi::GetClassName()
344 {
345     return className_;
346 }
347 } // namespace OHOS::FileManagement::CloudSync