• 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 #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 }