1 /*
2 * Copyright (c) 2022-2022 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 #define HST_LOG_TAG "Downloader"
16
17 #include "downloader.h"
18 #include <algorithm>
19
20 #include "foundation/log.h"
21 #include "http_curl_client.h"
22 #include "osal/utils/util.h"
23 #include "securec.h"
24 #include "steady_clock.h"
25
26 namespace OHOS {
27 namespace Media {
28 namespace Plugin {
29 namespace HttpPlugin {
30 namespace {
31 constexpr int PER_REQUEST_SIZE = 48 * 1024;
32 constexpr unsigned int SLEEP_TIME = 5; // Sleep 5ms
33 constexpr size_t RETRY_TIMES = 200; // Retry 200 times
34 constexpr size_t REQUEST_QUEUE_SIZE = 50;
35 }
36
DownloadRequest(const std::string & url,DataSaveFunc saveData,StatusCallbackFunc statusCallback,bool requestWholeFile)37 DownloadRequest::DownloadRequest(const std::string& url, DataSaveFunc saveData, StatusCallbackFunc statusCallback,
38 bool requestWholeFile)
39 : url_(url), saveData_(std::move(saveData)), statusCallback_(std::move(statusCallback)),
40 requestWholeFile_(requestWholeFile)
41 {
42 (void)memset_s(&headerInfo_, sizeof(HeaderInfo), 0x00, sizeof(HeaderInfo));
43 headerInfo_.fileContentLen = 0;
44 headerInfo_.contentLen = 0;
45 }
46
GetFileContentLength() const47 size_t DownloadRequest::GetFileContentLength() const
48 {
49 WaitHeaderUpdated();
50 return headerInfo_.GetFileContentLength();
51 }
52
SaveHeader(const HeaderInfo * header)53 void DownloadRequest::SaveHeader(const HeaderInfo* header)
54 {
55 headerInfo_.Update(header);
56 isHeaderUpdated = true;
57 }
58
IsChunked() const59 bool DownloadRequest::IsChunked() const
60 {
61 WaitHeaderUpdated();
62 return headerInfo_.isChunked;
63 };
64
IsEos() const65 bool DownloadRequest::IsEos() const
66 {
67 return isEos_;
68 }
69
GetRetryTimes()70 int DownloadRequest::GetRetryTimes()
71 {
72 return retryTimes_;
73 }
74
GetClientError()75 NetworkClientErrorCode DownloadRequest::GetClientError()
76 {
77 return clientError_;
78 }
79
GetServerError()80 NetworkServerErrorCode DownloadRequest::GetServerError()
81 {
82 return serverError_;
83 }
84
WaitHeaderUpdated() const85 void DownloadRequest::WaitHeaderUpdated() const
86 {
87 size_t times = 0;
88 while (!isHeaderUpdated && times < RETRY_TIMES) { // Wait Header(fileContentLen etc.) updated
89 OSAL::SleepFor(SLEEP_TIME);
90 times++;
91 }
92 MEDIA_LOG_D("isHeaderUpdated " PUBLIC_LOG_D32 ", times " PUBLIC_LOG_ZU, isHeaderUpdated, times);
93 }
94
Downloader(std::string name)95 Downloader::Downloader(std::string name) noexcept : name_(std::move(name))
96 {
97 shouldStartNextRequest = true;
98
99 client_ = std::make_shared<HttpCurlClient>(&RxHeaderData, &RxBodyData, this);
100 client_->Init();
101 requestQue_ = std::make_shared<BlockingQueue<std::shared_ptr<DownloadRequest>>>(name_ + "RequestQue",
102 REQUEST_QUEUE_SIZE);
103
104 task_ = std::make_shared<OSAL::Task>(std::string(name_ + "Downloader"));
105 task_->RegisterHandler([this] { HttpDownloadLoop(); });
106 }
107
Download(const std::shared_ptr<DownloadRequest> & request,int32_t waitMs)108 bool Downloader::Download(const std::shared_ptr<DownloadRequest>& request, int32_t waitMs)
109 {
110 MEDIA_LOG_I("In");
111 requestQue_->SetActive(true);
112 if (waitMs == -1) { // wait until push success
113 requestQue_->Push(request);
114 return true;
115 }
116 return requestQue_->Push(request, static_cast<int>(waitMs));
117 }
118
Start()119 void Downloader::Start()
120 {
121 MEDIA_LOG_I("Begin");
122 task_->Start();
123 MEDIA_LOG_I("End");
124 }
125
Pause()126 void Downloader::Pause()
127 {
128 MEDIA_LOG_I("Begin");
129 requestQue_->SetActive(false, false);
130 task_->Pause();
131 MEDIA_LOG_I("End");
132 }
133
Resume()134 void Downloader::Resume()
135 {
136 MEDIA_LOG_I("Begin");
137 requestQue_->SetActive(true);
138 currentRequest_->isEos_ = false;
139 Start();
140 MEDIA_LOG_I("End");
141 }
142
Stop()143 void Downloader::Stop()
144 {
145 MEDIA_LOG_I("Begin");
146 requestQue_->SetActive(false);
147 task_->Stop();
148 MEDIA_LOG_I("End");
149 }
150
Seek(int64_t offset)151 bool Downloader::Seek(int64_t offset)
152 {
153 MEDIA_LOG_I("Begin");
154 if (offset >= 0 && offset < currentRequest_->GetFileContentLength()) {
155 currentRequest_->startPos_ = offset;
156 }
157 int64_t temp = currentRequest_->GetFileContentLength() - currentRequest_->startPos_;
158 currentRequest_->requestSize_ = static_cast<int>(std::min(temp, static_cast<int64_t>(PER_REQUEST_SIZE)));
159 currentRequest_->isEos_ = false;
160 shouldStartNextRequest = false; // Reuse last request when seek
161 return true;
162 }
163
164 // Pause download thread before use currentRequest_
Retry(const std::shared_ptr<DownloadRequest> & request)165 bool Downloader::Retry(const std::shared_ptr<DownloadRequest>& request)
166 {
167 MEDIA_LOG_I(PUBLIC_LOG_S " Retry Begin, url : " PUBLIC_LOG_S, name_.c_str(), request->url_.c_str());
168 FALSE_RETURN_V(client_ != nullptr, false);
169 Pause();
170 client_->Close();
171 if (currentRequest_ != nullptr && currentRequest_->IsSame(request) && !shouldStartNextRequest) {
172 currentRequest_->retryTimes_++;
173 MEDIA_LOG_D("Do retry.");
174 }
175 client_->Open(currentRequest_->url_);
176 Resume();
177 MEDIA_LOG_I("Downloader Retry End");
178 return true;
179 }
180
BeginDownload()181 bool Downloader::BeginDownload()
182 {
183 MEDIA_LOG_I("Begin");
184 std::string url = currentRequest_->url_;
185 FALSE_RETURN_V(!url.empty(), false);
186
187 client_->Open(url);
188
189 currentRequest_->requestSize_ = 1;
190 currentRequest_->startPos_ = 0;
191 currentRequest_->isEos_ = false;
192 currentRequest_->retryTimes_ = 0;
193
194 MEDIA_LOG_I("End");
195 return true;
196 }
197
HttpDownloadLoop()198 void Downloader::HttpDownloadLoop()
199 {
200 if (shouldStartNextRequest) {
201 currentRequest_ = requestQue_->Pop(); // 1000);
202 if (!currentRequest_) {
203 return;
204 }
205 BeginDownload();
206 shouldStartNextRequest = false;
207 }
208 FALSE_RETURN_W(currentRequest_ != nullptr);
209 NetworkClientErrorCode clientCode = NetworkClientErrorCode::ERROR_OK;
210 NetworkServerErrorCode serverCode = 0;
211 long startPos = currentRequest_->startPos_;
212 if (currentRequest_->requestWholeFile_) {
213 startPos = -1;
214 }
215 Status ret = client_->RequestData(startPos, currentRequest_->requestSize_,
216 serverCode, clientCode);
217 currentRequest_->clientError_ = clientCode;
218 currentRequest_->serverError_ = serverCode;
219 if (ret == Status::OK) {
220 if (currentRequest_->retryTimes_ > 0) {
221 currentRequest_->retryTimes_ = 0;
222 }
223 int64_t remaining = currentRequest_->headerInfo_.fileContentLen - currentRequest_->startPos_;
224 if (currentRequest_->headerInfo_.fileContentLen > 0 && remaining <= 0) { // 检查是否播放结束
225 MEDIA_LOG_I("http transfer reach end, startPos_ " PUBLIC_LOG_D64 " url: " PUBLIC_LOG_S,
226 currentRequest_->startPos_, currentRequest_->url_.c_str());
227 currentRequest_->isEos_ = true;
228 if (requestQue_->Empty()) {
229 task_->PauseAsync();
230 }
231 shouldStartNextRequest = true;
232 } else if (remaining < PER_REQUEST_SIZE) {
233 currentRequest_->requestSize_ = remaining;
234 } else {
235 currentRequest_->requestSize_ = PER_REQUEST_SIZE;
236 }
237 } else {
238 MEDIA_LOG_E("Client request data failed. ret = " PUBLIC_LOG_D32 ", clientCode = " PUBLIC_LOG_D32,
239 static_cast<int32_t>(ret), static_cast<int32_t>(clientCode));
240 std::shared_ptr<Downloader> unused;
241 currentRequest_->statusCallback_(DownloadStatus::PARTTAL_DOWNLOAD, unused, currentRequest_);
242 task_->PauseAsync();
243 }
244 }
245
RxBodyData(void * buffer,size_t size,size_t nitems,void * userParam)246 size_t Downloader::RxBodyData(void* buffer, size_t size, size_t nitems, void* userParam)
247 {
248 auto mediaDownloader = static_cast<Downloader *>(userParam);
249 HeaderInfo* header = &(mediaDownloader->currentRequest_->headerInfo_);
250 size_t dataLen = size * nitems;
251
252 if (header->fileContentLen == 0) {
253 if (header->contentLen > 0) {
254 MEDIA_LOG_W("Unsupported range, use content length as content file length");
255 header->fileContentLen = header->contentLen;
256 } else {
257 MEDIA_LOG_E("fileContentLen and contentLen are both zero.");
258 return 0;
259 }
260 }
261 if (!mediaDownloader->currentRequest_->isDownloading_) {
262 mediaDownloader->currentRequest_->isDownloading_ = true;
263 }
264 if (!mediaDownloader->currentRequest_->saveData_(static_cast<uint8_t*>(buffer), dataLen,
265 mediaDownloader->currentRequest_->startPos_)) {
266 MEDIA_LOG_W("Save data failed.");
267 return 0; // save data failed, make perform finished.
268 }
269 mediaDownloader->currentRequest_->isDownloading_ = false;
270 MEDIA_LOG_I("RxBodyData: dataLen " PUBLIC_LOG_ZU ", startPos_ " PUBLIC_LOG_D64, dataLen,
271 mediaDownloader->currentRequest_->startPos_);
272 mediaDownloader->currentRequest_->startPos_ = mediaDownloader->currentRequest_->startPos_ + dataLen;
273
274 return dataLen;
275 }
276
277 namespace {
StringTrim(char * str)278 char* StringTrim(char* str)
279 {
280 if (str == nullptr) {
281 return nullptr;
282 }
283 char* p = str;
284 char* p1 = p + strlen(str) - 1;
285
286 while (*p && isspace(static_cast<int>(*p))) {
287 p++;
288 }
289 while (p1 > p && isspace(static_cast<int>(*p1))) {
290 *p1-- = 0;
291 }
292 return p;
293 }
294 }
295
RxHeaderData(void * buffer,size_t size,size_t nitems,void * userParam)296 size_t Downloader::RxHeaderData(void* buffer, size_t size, size_t nitems, void* userParam)
297 {
298 auto mediaDownloader = reinterpret_cast<Downloader *>(userParam);
299 HeaderInfo* info = &(mediaDownloader->currentRequest_->headerInfo_);
300 char* next = nullptr;
301 char* key = strtok_s(reinterpret_cast<char*>(buffer), ":", &next);
302 FALSE_RETURN_V(key != nullptr, size * nitems);
303 if (!strncmp(key, "Content-Type", strlen("Content-Type"))) {
304 char* token = strtok_s(nullptr, ":", &next);
305 FALSE_RETURN_V(token != nullptr, size * nitems);
306 char* type = StringTrim(token);
307 (void)memcpy_s(info->contentType, sizeof(info->contentType), type, sizeof(info->contentType));
308 }
309
310 if (!strncmp(key, "Content-Length", strlen("Content-Length")) ||
311 !strncmp(key, "content-length", strlen("content-length"))) {
312 char* token = strtok_s(nullptr, ":", &next);
313 FALSE_RETURN_V(token != nullptr, size * nitems);
314 char* contLen = StringTrim(token);
315 info->contentLen = atol(contLen);
316 }
317
318 if (!strncmp(key, "Transfer-Encoding", strlen("Transfer-Encoding")) ||
319 !strncmp(key, "transfer-encoding", strlen("transfer-encoding"))) {
320 char* token = strtok_s(nullptr, ":", &next);
321 FALSE_RETURN_V(token != nullptr, size * nitems);
322 char* transEncode = StringTrim(token);
323 if (!strncmp(transEncode, "chunked", strlen("chunked"))) {
324 info->isChunked = true;
325 }
326 }
327
328 if (!strncmp(key, "Content-Range", strlen("Content-Range")) ||
329 !strncmp(key, "content-range", strlen("content-range"))) {
330 char* token = strtok_s(nullptr, ":", &next);
331 FALSE_RETURN_V(token != nullptr, size * nitems);
332 char* strRange = StringTrim(token);
333 size_t start;
334 size_t end;
335 size_t fileLen;
336 FALSE_LOG_MSG(sscanf_s(strRange, "bytes %ld-%ld/%ld", &start, &end, &fileLen) != -1,
337 "sscanf get range failed");
338 if (info->fileContentLen > 0 && info->fileContentLen != fileLen) {
339 MEDIA_LOG_E("FileContentLen doesn't equal to fileLen");
340 }
341 if (info->fileContentLen == 0) {
342 info->fileContentLen = fileLen;
343 }
344 }
345 mediaDownloader->currentRequest_->SaveHeader(info);
346 return size * nitems;
347 }
348 }
349 }
350 }
351 }