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