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