* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "progress_thread.h"
#include <unistd.h>
#include <file_utils.h>
#include "curl/curl.h"
#include "curl/easy.h"
#include "firmware_common.h"
#include "update_define.h"
#include "update_log.h"
namespace OHOS {
namespace UpdateService {
std::atomic<bool> ProgressThread::isNoNet_{false};
std::atomic<bool> ProgressThread::isCancel_{false};
ProgressThread::~ProgressThread() {}
void ProgressThread::QuitDownloadThread()
{
std::unique_lock<std::mutex> lock(mutex_);
isWake_ = true;
isExitThread_ = true;
condition_.notify_one();
if (pDealThread_ != nullptr) {
pDealThread_->join();
delete pDealThread_;
pDealThread_ = nullptr;
}
}
int32_t ProgressThread::StartProgress()
{
std::unique_lock<std::mutex> lock(mutex_);
if (pDealThread_ == nullptr) {
pDealThread_ = new (std::nothrow)std::thread([this] { this->ExecuteThreadFunc(); });
ENGINE_CHECK(pDealThread_ != nullptr, return -1, "Failed to create thread");
}
ENGINE_LOGI("StartProgress");
isWake_ = true;
condition_.notify_one();
return 0;
}
void ProgressThread::StopProgress()
{
std::unique_lock<std::mutex> lock(mutex_);
isWake_ = true;
isExitThread_ = false;
condition_.notify_one();
}
void ProgressThread::ExecuteThreadFunc()
{
while (1) {
{
std::unique_lock<std::mutex> lock(mutex_);
while (!isWake_) {
ENGINE_LOGI("ExecuteThreadFunc wait");
condition_.wait(lock);
}
if (isExitThread_) {
break;
}
isWake_ = false;
}
if (!ProcessThreadExecute()) {
return;
}
}
ProcessThreadExit();
}
int32_t DownloadThread::StartDownload(const std::string &fileName, const std::string &url)
{
ENGINE_LOGI("StartDownload downloadFileName_ %s, serverUrl_ = %s", downloadFileName_.c_str(), serverUrl_.c_str());
downloadFileName_ = fileName;
serverUrl_ = url;
exitDownload_ = false;
curl_global_init(CURL_GLOBAL_ALL);
return StartProgress();
}
void DownloadThread::StopDownload()
{
ENGINE_LOGI("StopDownload");
exitDownload_ = true;
StopProgress();
curl_global_cleanup();
}
bool DownloadThread::ProcessThreadExecute()
{
ENGINE_LOGI("ProcessThreadExecute");
packageSize_ = GetLocalFileLength(downloadFileName_);
ENGINE_LOGI("download packageSize_: %zu ", packageSize_);
bool findDot = (downloadFileName_.find("/.") != std::string::npos) ||
(downloadFileName_.find("./") != std::string::npos);
ENGINE_CHECK(!findDot,
DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed to check file");
return true, "Failed to check file %s", downloadFileName_.c_str());
downloadFile_ = FileOpen(downloadFileName_, "ab+");
ENGINE_CHECK(downloadFile_ != nullptr,
DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed ot open file");
return true, "Failed to open file %s", downloadFileName_.c_str());
downloadHandle_ = curl_easy_init();
ENGINE_CHECK(downloadHandle_ != nullptr,
ProcessThreadExit();
DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed to init curl");
return true, "Failed to init curl");
curl_easy_setopt(downloadHandle_, CURLOPT_TIMEOUT, TIMEOUT_FOR_DOWNLOAD);
curl_easy_setopt(downloadHandle_, CURLOPT_CONNECTTIMEOUT, TIMEOUT_FOR_CONNECT);
curl_easy_setopt(downloadHandle_, CURLOPT_URL, serverUrl_.c_str());
curl_easy_setopt(downloadHandle_, CURLOPT_WRITEDATA, downloadFile_);
curl_easy_setopt(downloadHandle_, CURLOPT_WRITEFUNCTION, WriteFunc);
if (packageSize_ > 0) {
curl_easy_setopt(downloadHandle_, CURLOPT_RESUME_FROM_LARGE, static_cast<curl_off_t>(packageSize_));
}
curl_easy_setopt(downloadHandle_, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(downloadHandle_, CURLOPT_PROGRESSDATA, this);
curl_easy_setopt(downloadHandle_, CURLOPT_PROGRESSFUNCTION, DownloadProgress);
CURLcode res = curl_easy_perform(downloadHandle_);
if (res != CURLE_OK) {
ProcessThreadExit();
ENGINE_LOGI("Failed to download res %s", curl_easy_strerror(res));
if (res != CURLE_ABORTED_BY_CALLBACK) {
DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL,
std::to_string(CAST_INT(DownloadEndReason::CURL_ERROR)));
}
} else {
ProcessThreadExit();
ENGINE_LOGI("Success to download");
DownloadCallback(DOWNLOAD_FINISH_PERCENT, UpgradeStatus::DOWNLOAD_SUCCESS, "");
}
return false;
}
void DownloadThread::ProcessThreadExit()
{
ENGINE_LOGI("ProcessThreadExit");
if (downloadHandle_ != nullptr) {
curl_easy_cleanup(downloadHandle_);
}
downloadHandle_ = nullptr;
if (downloadFile_ != nullptr) {
fclose(downloadFile_);
}
downloadFile_ = nullptr;
}
int32_t DownloadThread::DownloadCallback(uint32_t percent, UpgradeStatus status, const std::string &error)
{
if (exitDownload_) {
ENGINE_LOGI("StopDownloadCallback");
return -1;
}
ENGINE_CHECK_NO_LOG(!DealAbnormal(percent),
ENGINE_LOGI("DealAbnormal");
return -1);
if (downloadProgress_.status == status && downloadProgress_.percent == percent) {
return 0;
}
ENGINE_LOGI("DownloadCallback percent %d, status %d, exitDownload_ %d, error %s, downloadFileName_ %s",
percent, CAST_INT(status), exitDownload_ ? 1 : 0, error.c_str(), downloadFileName_.c_str());
if (status == UpgradeStatus::DOWNLOAD_FAIL) {
if (access(downloadFileName_.c_str(), 0) == 0) {
unlink(downloadFileName_.c_str());
}
} else if (percent != DOWNLOAD_FINISH_PERCENT &&
(percent < (downloadProgress_.percent + DOWNLOAD_PERIOD_PERCENT))) {
return 0;
}
if (percent == DOWNLOAD_FINISH_PERCENT
&& status == UpgradeStatus::DOWNLOADING) {
return 0;
}
downloadProgress_.endReason = error;
downloadProgress_.percent = percent;
downloadProgress_.status = status;
if (callback_ != nullptr) {
callback_(serverUrl_, downloadFileName_, downloadProgress_);
}
return 0;
}
int32_t DownloadThread::DownloadProgress(const void *localData,
double dlTotal, double dlNow, double ulTotal, double ulNow)
{
ENGINE_CHECK_NO_LOG(dlTotal > 0, return 0);
auto engine = reinterpret_cast<DownloadThread*>(const_cast<void*>(localData));
ENGINE_CHECK(engine != nullptr, return -1, "Can not find engine");
double curr = engine->GetPackageSize();
unsigned int percent = (dlNow + curr) / (curr + dlTotal) * DOWNLOAD_FINISH_PERCENT;
return engine->DownloadCallback(percent, UpgradeStatus::DOWNLOADING, "");
}
size_t DownloadThread::WriteFunc(void *ptr, size_t size, size_t nmemb, const void *stream)
{
return fwrite(ptr, size, nmemb, reinterpret_cast<FILE*>(const_cast<void*>(stream)));
}
size_t DownloadThread::GetLocalFileLength(const std::string &fileName)
{
bool findDot = (fileName.find("/.") != std::string::npos) || (fileName.find("./") != std::string::npos);
ENGINE_CHECK_NO_LOG(!findDot, return 0);
FILE* fp = FileOpen(fileName, "r");
ENGINE_CHECK_NO_LOG(fp != nullptr, return 0);
int ret = fseek(fp, 0, SEEK_END);
ENGINE_CHECK_NO_LOG(ret == 0, fclose(fp);
return 0);
size_t length = (size_t)ftell(fp);
ret = fclose(fp);
ENGINE_CHECK_NO_LOG(ret == 0, return 0);
return length;
}
bool DownloadThread::DealAbnormal(uint32_t percent)
{
bool dealResult = false;
if (isNoNet_.load() || isCancel_.load()) {
ENGINE_LOGI("No network or user cancel");
downloadProgress_.endReason = isNoNet_.load() ?
std::to_string(CAST_INT(DownloadEndReason::NET_NOT_AVAILIABLE)) :
std::to_string(CAST_INT(DownloadEndReason::CANCEL));
downloadProgress_.percent = percent;
downloadProgress_.status = isNoNet_.load() ? UpgradeStatus::DOWNLOAD_FAIL :
UpgradeStatus::DOWNLOAD_CANCEL;
if (isCancel_.load()) {
isCancel_.store(false);
}
dealResult = true;
if (callback_ != nullptr) {
callback_(serverUrl_, downloadFileName_, downloadProgress_);
}
}
return dealResult;
}
FILE* DownloadThread::FileOpen(const std::string &fileName, const std::string &mode)
{
std::string realFileName = FileUtils::GetFileRealPath(fileName);
if (realFileName.empty()) {
ENGINE_LOGE("fileName is error");
return nullptr;
}
return fopen(realFileName.c_str(), mode.c_str());
}
}
}