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 if (!isCurlGlobalInited_) {
160 curl_global_init(CURL_GLOBAL_ALL);
161 isCurlGlobalInited_ = true;
162 }
163
164 thread_ = new (std::nothrow) std::thread(&DownloadTask::Run, this);
165 if (thread_ == nullptr) {
166 NotifyDone(false, "create download thread failed");
167 return;
168 }
169 thread_->detach();
170 }
171
Run()172 void DownloadTask::Run()
173 {
174 REQUEST_HILOGD("start download task");
175 pthread_setname_np(pthread_self(), "system_download");
176 uint32_t retryTime = 0;
177 bool result = false;
178 do {
179 if (GetFileSize(totalSize_)) {
180 result = DoDownload();
181 }
182 retryTime++;
183 REQUEST_HILOGD("download task retrytime: %{public}u, totalSize_: %{public}u", retryTime, totalSize_);
184 } while (!result && retryTime < RETRY_TIME);
185
186 if (retryTime >= RETRY_TIME) {
187 NotifyDone(false, "Network failed");
188 }
189 }
190
DoDownload()191 bool DownloadTask::DoDownload()
192 {
193 REQUEST_HILOGD("download task DoDownload");
194 curl_slist *headers{};
195 std::shared_ptr<CURL> handle(curl_easy_init(), [headers](CURL *handle) {
196 if (headers) {
197 curl_slist_free_all(headers);
198 }
199 curl_easy_cleanup(handle);
200 });
201
202 if (handle == nullptr) {
203 NotifyDone(false, "curl failed");
204 REQUEST_HILOGD("curl failed");
205 return false;
206 }
207
208 if (!SetOption(handle.get(), headers)) {
209 REQUEST_HILOGD("curl set option failed");
210 return false;
211 }
212 uint32_t localFileLength = GetLocalFileSize();
213 if (localFileLength > 0) {
214 if (localFileLength < totalSize_) {
215 SetResumeFromLarge(handle.get(), localFileLength);
216 } else {
217 NotifyDone(true, "Download task has already completed");
218 return true;
219 }
220 }
221
222 auto code = curl_easy_perform(handle.get());
223 REQUEST_HILOGI("code=%{public}d, %{public}s", code, errorBuffer_);
224 if (code == CURLE_OK) {
225 NotifyDone(code == CURLE_OK, errorBuffer_);
226 }
227 return code == CURLE_OK;
228 }
229
SetResumeFromLarge(CURL * curl,uint64_t pos)230 void DownloadTask::SetResumeFromLarge(CURL *curl, uint64_t pos)
231 {
232 curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, pos);
233 }
234 } // namespace OHOS::Request::Legacy