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