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 "legacy/download_task.h"
17
18 #include <pthread.h>
19 #include "constant.h"
20 #include "log.h"
21
22 namespace OHOS::Request::Legacy {
23 bool DownloadTask::isCurlGlobalInited_ = false;
24 const uint32_t DEFAULT_READ_TIMEOUT = 60;
25 const uint32_t DEFAULT_LOW_SPEED_LIMIT = 30;
26 constexpr uint32_t RETRY_TIME = 10;
DownloadTask(const std::string & token,const DownloadOption & option,const DoneFunc & callback)27 DownloadTask::DownloadTask(const std::string &token, const DownloadOption &option, const DoneFunc &callback)
28 : taskId_(token), option_(option), callback_(callback), totalSize_(0), hasFileSize_(false)
29 {
30 REQUEST_HILOGI("constructor");
31 }
32
~DownloadTask()33 DownloadTask::~DownloadTask()
34 {
35 REQUEST_HILOGI("destroy");
36 if (filp_ != nullptr) {
37 fclose(filp_);
38 }
39 delete[] errorBuffer_;
40 delete thread_;
41 }
42
OpenDownloadFile() const43 FILE *DownloadTask::OpenDownloadFile() const
44 {
45 auto downloadFile = option_.fileDir_ + '/' + option_.filename_;
46 FILE *filp = fopen(downloadFile.c_str(), "w+");
47 if (filp == nullptr) {
48 REQUEST_HILOGE("open download file failed");
49 }
50 return filp;
51 }
52
GetLocalFileSize()53 uint32_t DownloadTask::GetLocalFileSize()
54 {
55 if (filp_ == nullptr) {
56 filp_ = OpenDownloadFile();
57 if (filp_ == nullptr) {
58 return 0;
59 }
60 }
61
62 int nRet = fseek(filp_, 0, SEEK_END);
63 if (nRet != 0) {
64 REQUEST_HILOGE("fseek error");
65 return 0;
66 }
67 long lRet = ftell(filp_);
68 if (lRet < 0) {
69 REQUEST_HILOGE("ftell error");
70 return 0;
71 }
72 return static_cast<uint32_t>(lRet);
73 }
NotifyDone(bool successful,const std::string & errMsg)74 void DownloadTask::NotifyDone(bool successful, const std::string &errMsg)
75 {
76 if (filp_ != nullptr) {
77 fclose(filp_);
78 filp_ = nullptr;
79
80 if (!successful) {
81 REQUEST_HILOGE("remove download file");
82 remove((option_.fileDir_ + '/' + option_.filename_).c_str());
83 }
84 }
85
86 if (callback_) {
87 callback_(taskId_, successful, errMsg);
88 }
89 }
90
GetFileSize(uint32_t & result)91 bool DownloadTask::GetFileSize(uint32_t &result)
92 {
93 if (hasFileSize_) {
94 REQUEST_HILOGD("Already get file size");
95 return true;
96 }
97 std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), curl_easy_cleanup);
98
99 if (!handle) {
100 REQUEST_HILOGD("Failed to create download service task");
101 return false;
102 }
103
104 curl_easy_setopt(handle.get(), CURLOPT_URL, option_.url_.c_str());
105 curl_easy_setopt(handle.get(), CURLOPT_HEADER, 1L);
106 curl_easy_setopt(handle.get(), CURLOPT_NOBODY, 1L);
107 CURLcode code = curl_easy_perform(handle.get());
108 double size = 0.0;
109 curl_easy_getinfo(handle.get(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size);
110
111 if (code == CURLE_OK) {
112 if (size > UINT_MAX) {
113 REQUEST_HILOGD("file size overflow");
114 return false;
115 }
116 result = static_cast<uint32_t>(size);
117 if (result == static_cast<uint32_t>(-1)) {
118 result = 0;
119 }
120 hasFileSize_ = true;
121 REQUEST_HILOGD("Has got file size");
122 }
123 REQUEST_HILOGD("fetch file size %{public}d", result);
124 return hasFileSize_;
125 }
126
SetOption(CURL * handle,curl_slist * & headers)127 bool DownloadTask::SetOption(CURL *handle, curl_slist *&headers)
128 {
129 filp_ = OpenDownloadFile();
130 if (filp_ == nullptr) {
131 return false;
132 }
133 curl_easy_setopt(handle, CURLOPT_WRITEDATA, filp_);
134
135 errorBuffer_ = new (std::nothrow) char[CURL_ERROR_SIZE];
136 if (errorBuffer_ == nullptr) {
137 return false;
138 }
139 curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorBuffer_);
140
141 curl_easy_setopt(handle, CURLOPT_URL, option_.url_.c_str());
142 curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
143 curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
144 curl_easy_setopt(handle, CURLOPT_LOW_SPEED_TIME, DEFAULT_READ_TIMEOUT);
145 curl_easy_setopt(handle, CURLOPT_LOW_SPEED_LIMIT, DEFAULT_LOW_SPEED_LIMIT);
146
147 if (!option_.header_.empty()) {
148 for (const auto &head : option_.header_) {
149 headers = curl_slist_append(headers, head.c_str());
150 }
151 curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
152 }
153 return true;
154 }
155
Start()156 void DownloadTask::Start()
157 {
158 REQUEST_HILOGD("taskId=%{public}s url=%{public}s file=%{public}s dir=%{public}s", taskId_.c_str(),
159 option_.url_.c_str(), option_.filename_.c_str(), option_.fileDir_.c_str());
160 if (!isCurlGlobalInited_) {
161 curl_global_init(CURL_GLOBAL_ALL);
162 isCurlGlobalInited_ = true;
163 }
164
165 thread_ = new (std::nothrow) std::thread(&DownloadTask::Run, this);
166 if (thread_ == nullptr) {
167 NotifyDone(false, "create download thread failed");
168 return;
169 }
170 thread_->detach();
171 }
172
Run()173 void DownloadTask::Run()
174 {
175 REQUEST_HILOGD("start download task");
176 pthread_setname_np(pthread_self(), "system_download");
177 uint32_t retryTime = 0;
178 bool result = false;
179 do {
180 if (GetFileSize(totalSize_)) {
181 result = DoDownload();
182 }
183 retryTime++;
184 REQUEST_HILOGD("download task retrytime: %{public}u, totalSize_: %{public}u", retryTime, totalSize_);
185 } while (!result && retryTime < RETRY_TIME);
186
187 if (retryTime >= RETRY_TIME) {
188 NotifyDone(false, "Network failed");
189 }
190 }
191
DoDownload()192 bool DownloadTask::DoDownload()
193 {
194 REQUEST_HILOGD("download task DoDownload");
195 curl_slist *headers{};
196 std::shared_ptr<CURL> handle(curl_easy_init(), [headers](CURL *handle) {
197 if (headers) {
198 curl_slist_free_all(headers);
199 }
200 curl_easy_cleanup(handle);
201 });
202
203 if (handle == nullptr) {
204 NotifyDone(false, "curl failed");
205 REQUEST_HILOGD("curl failed");
206 return false;
207 }
208
209 if (!SetOption(handle.get(), headers)) {
210 REQUEST_HILOGD("curl set option failed");
211 return false;
212 }
213 uint32_t localFileLength = GetLocalFileSize();
214 if (localFileLength > 0) {
215 if (localFileLength < totalSize_) {
216 SetResumeFromLarge(handle.get(), localFileLength);
217 } else {
218 NotifyDone(true, "Download task has already completed");
219 return true;
220 }
221 }
222
223 auto code = curl_easy_perform(handle.get());
224 REQUEST_HILOGI("code=%{public}d, %{public}s", code, errorBuffer_);
225 if (code == CURLE_OK) {
226 NotifyDone(code == CURLE_OK, errorBuffer_);
227 }
228 return code == CURLE_OK;
229 }
230
SetResumeFromLarge(CURL * curl,uint64_t pos)231 void DownloadTask::SetResumeFromLarge(CURL *curl, uint64_t pos)
232 {
233 curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, pos);
234 }
235 } // namespace OHOS::Request::Legacy