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