1 /*
2  * Copyright (C) 2024 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 "js_notify_data_listener.h"
17 
18 #include <numeric>
19 
20 #include "js_task.h"
21 #include "log.h"
22 #include "napi/native_node_api.h"
23 #include "napi_utils.h"
24 #include "request_event.h"
25 #include "request_manager.h"
26 
27 namespace OHOS::Request {
28 
AddListener(napi_value cb)29 napi_status JSNotifyDataListener::AddListener(napi_value cb)
30 {
31     napi_status ret = this->AddListenerInner(cb);
32     if (ret != napi_ok) {
33         return ret;
34     }
35     /* remove listener must be subscribed to free task */
36     if (this->validCbNum == 1 && this->type_ != SubscribeType::REMOVE) {
37         RequestManager::GetInstance()->AddListener(this->taskId_, this->type_, shared_from_this());
38     }
39     return napi_ok;
40 }
41 
RemoveListener(napi_value cb)42 napi_status JSNotifyDataListener::RemoveListener(napi_value cb)
43 {
44     napi_status ret = this->RemoveListenerInner(cb);
45     if (ret != napi_ok) {
46         return ret;
47     }
48     if (this->validCbNum == 0 && this->type_ != SubscribeType::REMOVE) {
49         RequestManager::GetInstance()->RemoveListener(this->taskId_, this->type_, shared_from_this());
50     }
51     return napi_ok;
52 }
53 
IsHeaderReceive(const std::shared_ptr<NotifyData> & notifyData)54 bool JSNotifyDataListener::IsHeaderReceive(const std::shared_ptr<NotifyData> &notifyData)
55 {
56     if (notifyData->version == Version::API9 && notifyData->action == Action::UPLOAD
57         && notifyData->type == SubscribeType::HEADER_RECEIVE) {
58         return true;
59     } else if (notifyData->version == Version::API10 && notifyData->action == Action::UPLOAD
60                && notifyData->progress.state == State::COMPLETED
61                && (notifyData->type == SubscribeType::PROGRESS || notifyData->type == SubscribeType::COMPLETED)) {
62         return true;
63     }
64     return false;
65 }
66 
ProcessHeaderReceive(const std::shared_ptr<NotifyData> & notifyData)67 void JSNotifyDataListener::ProcessHeaderReceive(const std::shared_ptr<NotifyData> &notifyData)
68 {
69     uint32_t index = notifyData->progress.index;
70     size_t len = 0;
71     std::string filePath;
72     {
73         std::lock_guard<std::mutex> lockGuard(JsTask::taskMutex_);
74         auto item = JsTask::taskMap_.find(std::to_string(notifyData->taskId));
75         if (item == JsTask::taskMap_.end()) {
76             REQUEST_HILOGE("Task ID not found");
77             return;
78         }
79         JsTask *task = item->second;
80         len = task->config_.bodyFileNames.size();
81         if (index >= len) {
82             return;
83         }
84         filePath = task->config_.bodyFileNames[index];
85     }
86 
87     NapiUtils::ReadBytesFromFile(filePath, notifyData->progress.bodyBytes);
88     // Waiting for "complete" to read and delete.
89     if (!(notifyData->version == Version::API10 && index + 1 == len && notifyData->type == SubscribeType::PROGRESS)) {
90         NapiUtils::RemoveFile(filePath);
91     }
92 }
93 
NotifyDataProcess(const std::shared_ptr<NotifyData> & notifyData,napi_value * value,uint32_t & paramNumber)94 void JSNotifyDataListener::NotifyDataProcess(
95     const std::shared_ptr<NotifyData> &notifyData, napi_value *value, uint32_t &paramNumber)
96 {
97     if (IsHeaderReceive(notifyData)) {
98         ProcessHeaderReceive(notifyData);
99     }
100 
101     if (notifyData->version == Version::API10) {
102         REQUEST_HILOGD("Receive API10 callback");
103         value[0] = NapiUtils::Convert2JSValue(this->env_, notifyData->progress);
104         return;
105     }
106 
107     if (notifyData->action == Action::DOWNLOAD) {
108         if (notifyData->type == SubscribeType::PROGRESS) {
109             value[0] = NapiUtils::Convert2JSValue(this->env_, notifyData->progress.processed);
110             if (!notifyData->progress.sizes.empty()) {
111                 value[1] = NapiUtils::Convert2JSValue(this->env_, notifyData->progress.sizes[0]);
112                 paramNumber = NapiUtils::TWO_ARG;
113             }
114         } else if (notifyData->type == SubscribeType::FAILED) {
115             if (notifyData->taskStates.empty()) {
116                 paramNumber = 0;
117                 return;
118             }
119             int64_t failedReason;
120             auto it = RequestEvent::failMap_.find(static_cast<Reason>(notifyData->taskStates[0].responseCode));
121             if (it != RequestEvent::failMap_.end()) {
122                 failedReason = it->second;
123             } else {
124                 failedReason = static_cast<int64_t>(ERROR_UNKNOWN);
125             }
126             value[0] = NapiUtils::Convert2JSValue(this->env_, failedReason);
127         }
128     } else if (notifyData->action == Action::UPLOAD) {
129         if (notifyData->type == SubscribeType::COMPLETED || notifyData->type == SubscribeType::FAILED) {
130             value[0] = NapiUtils::Convert2JSValue(env_, notifyData->taskStates);
131         } else if (notifyData->type == SubscribeType::PROGRESS) {
132             int64_t totalSize =
133                 std::accumulate(notifyData->progress.sizes.begin(), notifyData->progress.sizes.end(), 0);
134             value[0] = NapiUtils::Convert2JSValue(this->env_, notifyData->progress.totalProcessed);
135             value[1] = NapiUtils::Convert2JSValue(this->env_, totalSize);
136             paramNumber = NapiUtils::TWO_ARG;
137         } else if (notifyData->type == SubscribeType::HEADER_RECEIVE) {
138             value[0] = NapiUtils::Convert2JSHeadersAndBody(
139                 env_, notifyData->progress.extras, notifyData->progress.bodyBytes, true);
140         }
141     }
142 }
143 
SubscribeTypeToString(SubscribeType type)144 static std::string SubscribeTypeToString(SubscribeType type)
145 {
146     switch (type) {
147         case SubscribeType::COMPLETED:
148             return "completed";
149         case SubscribeType::FAILED:
150             return "failed";
151         case SubscribeType::HEADER_RECEIVE:
152             return "header_receive";
153         case SubscribeType::PAUSE:
154             return "pause";
155         case SubscribeType::PROGRESS:
156             return "progress";
157         case SubscribeType::REMOVE:
158             return "remove";
159         case SubscribeType::RESUME:
160             return "resume";
161         case SubscribeType::RESPONSE:
162             return "response";
163         case SubscribeType::BUTT:
164             return "butt";
165     }
166 }
167 
CheckRemoveJSTask(const std::shared_ptr<NotifyData> & notifyData,const std::string & tid)168 static RemoveTaskChecker CheckRemoveJSTask(const std::shared_ptr<NotifyData> &notifyData, const std::string &tid)
169 {
170     if (notifyData->version == Version::API9
171         && (notifyData->type == SubscribeType::COMPLETED || notifyData->type == SubscribeType::FAILED
172             || notifyData->type == SubscribeType::REMOVE)) {
173         return RemoveTaskChecker::ClearFileAndRemoveTask;
174     } else if (notifyData->version == Version::API10) {
175         if (notifyData->type == SubscribeType::REMOVE) {
176             return RemoveTaskChecker::ClearFileAndRemoveTask;
177         } else if (notifyData->type == SubscribeType::COMPLETED || notifyData->type == SubscribeType::FAILED) {
178             return RemoveTaskChecker::ClearFile;
179         }
180     }
181     return RemoveTaskChecker::DoNothing;
182 }
183 
DoJSTask(const std::shared_ptr<NotifyData> & notifyData)184 void JSNotifyDataListener::DoJSTask(const std::shared_ptr<NotifyData> &notifyData)
185 {
186     std::string tid = std::to_string(notifyData->taskId);
187     uint32_t paramNumber = NapiUtils::ONE_ARG;
188     napi_value values[NapiUtils::TWO_ARG] = { nullptr };
189     // Data from file to memory.
190     this->NotifyDataProcess(notifyData, values, paramNumber);
191     RemoveTaskChecker checkDo = CheckRemoveJSTask(notifyData, tid);
192     if (checkDo == RemoveTaskChecker::DoNothing) {
193         this->OnMessageReceive(values, paramNumber);
194     } else if (checkDo == RemoveTaskChecker::ClearFile) {
195         JsTask::ClearTaskTemp(tid, true, false, false);
196         REQUEST_HILOGD("jstask %{public}s clear file", tid.c_str());
197         this->OnMessageReceive(values, paramNumber);
198     } else if (checkDo == RemoveTaskChecker::ClearFileAndRemoveTask) {
199         JsTask::ClearTaskTemp(tid, true, true, true);
200         REQUEST_HILOGD("jstask %{public}s clear file", tid.c_str());
201         this->OnMessageReceive(values, paramNumber);
202         JsTask::RemoveTaskContext(tid);
203         JsTask::ClearTaskMap(tid);
204         REQUEST_HILOGD("jstask %{public}s removed", tid.c_str());
205     }
206 }
207 
OnNotifyDataReceive(const std::shared_ptr<NotifyData> & notifyData)208 void JSNotifyDataListener::OnNotifyDataReceive(const std::shared_ptr<NotifyData> &notifyData)
209 {
210     REQUEST_HILOGI(
211         "Notify %{public}s tid %{public}d", SubscribeTypeToString(notifyData->type).c_str(), notifyData->taskId);
212     NotifyDataPtr *ptr = new (std::nothrow) NotifyDataPtr;
213     if (ptr == nullptr) {
214         REQUEST_HILOGE("NotifyDataPtr new failed");
215         return;
216     }
217     ptr->listener = shared_from_this();
218     ptr->notifyData = notifyData;
219 
220     int32_t ret = napi_send_event(
221         this->env_,
222         [ptr]() {
223             uint32_t paramNumber = NapiUtils::ONE_ARG;
224             napi_handle_scope scope = nullptr;
225             napi_open_handle_scope(ptr->listener->env_, &scope);
226             if (scope == nullptr) {
227                 delete ptr;
228                 return;
229             }
230             ptr->listener->DoJSTask(ptr->notifyData);
231             napi_close_handle_scope(ptr->listener->env_, scope);
232             delete ptr;
233         },
234         napi_eprio_high);
235     if (ret != napi_ok) {
236         REQUEST_HILOGE("napi_send_event failed: %{public}d", ret);
237         delete ptr;
238     }
239 }
240 
241 } // namespace OHOS::Request