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 * 10;
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() const67 int DownloadRequest::GetRetryTimes() const
68 {
69 return retryTimes_;
70 }
71
GetClientError() const72 NetworkClientErrorCode DownloadRequest::GetClientError() const
73 {
74 return clientError_;
75 }
76
GetServerError() const77 NetworkServerErrorCode DownloadRequest::GetServerError() const
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 = static_cast<int64_t>(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 {
279 if (currentRequest_->retryTimes_ > 0) {
280 currentRequest_->retryTimes_ = 0;
281 }
282 int64_t remaining = static_cast<int64_t>(currentRequest_->headerInfo_.fileContentLen) -
283 currentRequest_->startPos_;
284 if (currentRequest_->headerInfo_.fileContentLen > 0 && remaining <= 0) { // 检查是否播放结束
285 MEDIA_LOG_I("http transfer reach end, startPos_ " PUBLIC_LOG_D64 " url: " PUBLIC_LOG_S,
286 currentRequest_->startPos_, currentRequest_->url_.c_str());
287 currentRequest_->isEos_ = true;
288 if (requestQue_->Empty()) {
289 task_->PauseAsync();
290 }
291 shouldStartNextRequest = true;
292 return;
293 }
294 if (currentRequest_->headerInfo_.fileContentLen == 0 && remaining <= 0) {
295 currentRequest_->isEos_ = true;
296 currentRequest_->Close();
297 if (requestQue_->Empty()) {
298 task_->PauseAsync();
299 }
300 shouldStartNextRequest = true;
301 return;
302 }
303 if (remaining < PER_REQUEST_SIZE) {
304 currentRequest_->requestSize_ = remaining;
305 } else {
306 currentRequest_->requestSize_ = PER_REQUEST_SIZE;
307 }
308 }
309
RxBodyData(void * buffer,size_t size,size_t nitems,void * userParam)310 size_t Downloader::RxBodyData(void* buffer, size_t size, size_t nitems, void* userParam)
311 {
312 auto mediaDownloader = static_cast<Downloader *>(userParam);
313 HeaderInfo* header = &(mediaDownloader->currentRequest_->headerInfo_);
314 size_t dataLen = size * nitems;
315 if (header->fileContentLen == 0) {
316 if (header->contentLen > 0) {
317 MEDIA_LOG_W("Unsupported range, use content length as content file length");
318 header->fileContentLen = header->contentLen;
319 } else {
320 MEDIA_LOG_E("fileContentLen and contentLen are both zero.");
321 return 0;
322 }
323 }
324 if (!mediaDownloader->currentRequest_->isDownloading_) {
325 mediaDownloader->currentRequest_->isDownloading_ = true;
326 }
327 if (!mediaDownloader->currentRequest_->saveData_(static_cast<uint8_t *>(buffer), dataLen)) {
328 MEDIA_LOG_W("Save data failed.");
329 return 0; // save data failed, make perform finished.
330 }
331 mediaDownloader->currentRequest_->isDownloading_ = false;
332 MEDIA_LOG_I("RxBodyData: dataLen " PUBLIC_LOG_ZU ", startPos_ " PUBLIC_LOG_D64, dataLen,
333 mediaDownloader->currentRequest_->startPos_);
334 mediaDownloader->currentRequest_->startPos_ = mediaDownloader->currentRequest_->startPos_ + dataLen;
335
336 return dataLen;
337 }
338
339 namespace {
StringTrim(char * str)340 char* StringTrim(char* str)
341 {
342 if (str == nullptr) {
343 return nullptr;
344 }
345 char* p = str;
346 char* p1 = p + strlen(str) - 1;
347
348 while (*p && isspace(static_cast<int>(*p))) {
349 p++;
350 }
351 while (p1 > p && isspace(static_cast<int>(*p1))) {
352 *p1-- = 0;
353 }
354 return p;
355 }
356 }
357
RxHeaderData(void * buffer,size_t size,size_t nitems,void * userParam)358 size_t Downloader::RxHeaderData(void* buffer, size_t size, size_t nitems, void* userParam)
359 {
360 auto mediaDownloader = reinterpret_cast<Downloader *>(userParam);
361 HeaderInfo* info = &(mediaDownloader->currentRequest_->headerInfo_);
362 char* next = nullptr;
363 char* key = strtok_s(reinterpret_cast<char*>(buffer), ":", &next);
364 FALSE_RETURN_V(key != nullptr, size * nitems);
365 if (!strncmp(key, "Content-Type", strlen("Content-Type"))) {
366 char* token = strtok_s(nullptr, ":", &next);
367 FALSE_RETURN_V(token != nullptr, size * nitems);
368 char* type = StringTrim(token);
369 (void)memcpy_s(info->contentType, sizeof(info->contentType), type, sizeof(info->contentType));
370 }
371
372 if (!strncmp(key, "Content-Length", strlen("Content-Length")) ||
373 !strncmp(key, "content-length", strlen("content-length"))) {
374 char* token = strtok_s(nullptr, ":", &next);
375 FALSE_RETURN_V(token != nullptr, size * nitems);
376 char* contLen = StringTrim(token);
377 info->contentLen = atol(contLen);
378 }
379
380 if (!strncmp(key, "Transfer-Encoding", strlen("Transfer-Encoding")) ||
381 !strncmp(key, "transfer-encoding", strlen("transfer-encoding"))) {
382 char* token = strtok_s(nullptr, ":", &next);
383 FALSE_RETURN_V(token != nullptr, size * nitems);
384 char* transEncode = StringTrim(token);
385 if (!strncmp(transEncode, "chunked", strlen("chunked"))) {
386 info->isChunked = true;
387 }
388 }
389
390 if (!strncmp(key, "Content-Range", strlen("Content-Range")) ||
391 !strncmp(key, "content-range", strlen("content-range"))) {
392 char* token = strtok_s(nullptr, ":", &next);
393 FALSE_RETURN_V(token != nullptr, size * nitems);
394 char* strRange = StringTrim(token);
395 size_t start;
396 size_t end;
397 size_t fileLen;
398 FALSE_LOG_MSG(sscanf_s(strRange, "bytes %uld-%uld/%uld", &start, &end, &fileLen) != -1,
399 "sscanf get range failed");
400 if (info->fileContentLen > 0 && info->fileContentLen != fileLen) {
401 MEDIA_LOG_E("FileContentLen doesn't equal to fileLen");
402 }
403 if (info->fileContentLen == 0) {
404 info->fileContentLen = fileLen;
405 }
406 }
407 mediaDownloader->currentRequest_->SaveHeader(info);
408 return size * nitems;
409 }
410 }
411 }
412 }
413 }
414