• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }