1/*
2 * Copyright (c) 2021 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
16#include "progress_thread.h"
17
18#include <unistd.h>
19#include <file_utils.h>
20
21#include "curl/curl.h"
22#include "curl/easy.h"
23
24#include "firmware_common.h"
25#include "update_define.h"
26#include "update_log.h"
27
28namespace OHOS {
29namespace UpdateEngine {
30bool ProgressThread::isNoNet_ = false;
31bool ProgressThread::isCancel_ = false;
32
33ProgressThread::~ProgressThread() {}
34
35void ProgressThread::QuitDownloadThread()
36{
37    {
38        std::unique_lock<std::mutex> lock(mutex_);
39        isWake_ = true;
40        isExitThread_ = true;
41        condition_.notify_one();
42    }
43    if (pDealThread_ != nullptr) {
44        pDealThread_->join();
45        delete pDealThread_;
46        pDealThread_ = nullptr;
47    }
48}
49
50int32_t ProgressThread::StartProgress()
51{
52    std::unique_lock<std::mutex> lock(mutex_);
53    if (pDealThread_ == nullptr) {
54        pDealThread_ = new (std::nothrow)std::thread([this] { this->ExecuteThreadFunc(); });
55        ENGINE_CHECK(pDealThread_ != nullptr, return -1, "Failed to create thread");
56    }
57    ENGINE_LOGI("StartProgress");
58    isWake_ = true;
59    condition_.notify_one();
60    return 0;
61}
62
63void ProgressThread::StopProgress()
64{
65    std::unique_lock<std::mutex> lock(mutex_);
66    isWake_ = true;
67    isExitThread_ = false;
68    condition_.notify_one();
69}
70
71void ProgressThread::ExecuteThreadFunc()
72{
73    while (1) {
74        {
75            std::unique_lock<std::mutex> lock(mutex_);
76            while (!isWake_) {
77                ENGINE_LOGI("ExecuteThreadFunc wait");
78                condition_.wait(lock);
79            }
80            if (isExitThread_) {
81                break;
82            }
83            isWake_ = false;
84        }
85        if (!ProcessThreadExecute()) {
86            return;
87        }
88    }
89    // thread exit and release resource
90    ProcessThreadExit();
91}
92
93int32_t DownloadThread::StartDownload(const std::string &fileName, const std::string &url)
94{
95    ENGINE_LOGI("StartDownload downloadFileName_ %s, serverUrl_ = %s", downloadFileName_.c_str(), serverUrl_.c_str());
96    downloadFileName_ = fileName;
97    serverUrl_ = url;
98    exitDownload_ = false;
99    curl_global_init(CURL_GLOBAL_ALL);
100    return StartProgress();
101}
102
103void DownloadThread::StopDownload()
104{
105    ENGINE_LOGI("StopDownload");
106    exitDownload_ = true;
107    StopProgress();
108    curl_global_cleanup();
109}
110
111bool DownloadThread::ProcessThreadExecute()
112{
113    ENGINE_LOGI("ProcessThreadExecute");
114    packageSize_ = GetLocalFileLength(downloadFileName_);
115    ENGINE_LOGI("download  packageSize_: %zu ", packageSize_);
116    bool findDot = (downloadFileName_.find("/.") != std::string::npos) ||
117        (downloadFileName_.find("./") != std::string::npos);
118    ENGINE_CHECK(!findDot,
119        DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed to check file");
120        return true, "Failed to check file %s", downloadFileName_.c_str());
121    downloadFile_ = FileOpen(downloadFileName_, "ab+");
122    ENGINE_CHECK(downloadFile_ != nullptr,
123        DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed ot open file");
124        return true, "Failed to open file %s", downloadFileName_.c_str());
125
126    downloadHandle_ = curl_easy_init();
127    ENGINE_CHECK(downloadHandle_ != nullptr,
128        ProcessThreadExit();
129        DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed to init curl");
130        return true, "Failed to init curl");
131
132    curl_easy_setopt(downloadHandle_, CURLOPT_TIMEOUT, TIMEOUT_FOR_DOWNLOAD);
133    curl_easy_setopt(downloadHandle_, CURLOPT_CONNECTTIMEOUT, TIMEOUT_FOR_CONNECT);
134    curl_easy_setopt(downloadHandle_, CURLOPT_URL, serverUrl_.c_str());
135    curl_easy_setopt(downloadHandle_, CURLOPT_WRITEDATA, downloadFile_);
136    curl_easy_setopt(downloadHandle_, CURLOPT_WRITEFUNCTION, WriteFunc);
137    if (packageSize_ > 0) {
138        curl_easy_setopt(downloadHandle_, CURLOPT_RESUME_FROM_LARGE, static_cast<curl_off_t>(packageSize_));
139    }
140    curl_easy_setopt(downloadHandle_, CURLOPT_NOPROGRESS, 0L);
141    curl_easy_setopt(downloadHandle_, CURLOPT_PROGRESSDATA, this);
142    curl_easy_setopt(downloadHandle_, CURLOPT_PROGRESSFUNCTION, DownloadProgress);
143    CURLcode res = curl_easy_perform(downloadHandle_);
144    if (res != CURLE_OK) {
145        ProcessThreadExit();
146        ENGINE_LOGI("Failed to download res %s", curl_easy_strerror(res));
147        if (res != CURLE_ABORTED_BY_CALLBACK) { // cancel by user, do not callback
148            DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL,
149                std::to_string(CAST_INT(DownloadEndReason::CURL_ERROR)));
150        }
151    } else {
152        ProcessThreadExit();
153        ENGINE_LOGI("Success to download");
154        DownloadCallback(DOWNLOAD_FINISH_PERCENT, UpgradeStatus::DOWNLOAD_SUCCESS, "");
155    }
156    return false;
157}
158
159void DownloadThread::ProcessThreadExit()
160{
161    ENGINE_LOGI("ProcessThreadExit");
162    if (downloadHandle_ != nullptr) {
163        curl_easy_cleanup(downloadHandle_);
164    }
165    downloadHandle_ = nullptr;
166    if (downloadFile_ != nullptr) {
167        fclose(downloadFile_);
168    }
169    downloadFile_ = nullptr;
170}
171
172int32_t DownloadThread::DownloadCallback(uint32_t percent, UpgradeStatus status, const std::string &error)
173{
174    if (exitDownload_) {
175        ENGINE_LOGI("StopDownloadCallback");
176        return -1;
177    }
178    ENGINE_CHECK_NO_LOG(!DealAbnormal(percent),
179        ENGINE_LOGI("DealAbnormal");
180        return -1);
181    if (downloadProgress_.status == status && downloadProgress_.percent == percent) {
182        // 避免回调过于频繁
183        return 0;
184    }
185    ENGINE_LOGI("DownloadCallback percent %d, status %d, exitDownload_ %d, error %s, downloadFileName_ %s",
186        percent, CAST_INT(status), exitDownload_ ? 1 : 0,  error.c_str(), downloadFileName_.c_str());
187    if (status == UpgradeStatus::DOWNLOAD_FAIL) {
188        if (access(downloadFileName_.c_str(), 0) == 0) {
189            unlink(downloadFileName_.c_str());
190        }
191    } else if (percent != DOWNLOAD_FINISH_PERCENT &&
192               (percent < (downloadProgress_.percent + DOWNLOAD_PERIOD_PERCENT))) {
193        return 0;
194    }
195
196    // wait until the download is complete, and then make a notification
197    if (percent == DOWNLOAD_FINISH_PERCENT
198        && status == UpgradeStatus::DOWNLOADING) {
199        return 0;
200    }
201    downloadProgress_.endReason = error;
202    downloadProgress_.percent = percent;
203    downloadProgress_.status = status;
204    if (callback_ != nullptr) {
205        callback_(serverUrl_, downloadFileName_, downloadProgress_);
206    }
207    return 0;
208}
209
210int32_t DownloadThread::DownloadProgress(const void *localData,
211    double dlTotal, double dlNow, double ulTotal, double ulNow)
212{
213    ENGINE_CHECK_NO_LOG(dlTotal > 0, return 0);
214    auto engine = reinterpret_cast<DownloadThread*>(const_cast<void*>(localData));
215    ENGINE_CHECK(engine != nullptr, return -1, "Can not find engine");
216    double curr = engine->GetPackageSize();
217    unsigned int percent = (dlNow + curr) / (curr + dlTotal) * DOWNLOAD_FINISH_PERCENT;
218    return engine->DownloadCallback(percent, UpgradeStatus::DOWNLOADING, "");
219}
220
221size_t DownloadThread::WriteFunc(void *ptr, size_t size, size_t nmemb, const void *stream)
222{
223    return fwrite(ptr, size, nmemb, reinterpret_cast<FILE*>(const_cast<void*>(stream)));
224}
225
226size_t DownloadThread::GetLocalFileLength(const std::string &fileName)
227{
228    bool findDot = (fileName.find("/.") != std::string::npos) || (fileName.find("./") != std::string::npos);
229    ENGINE_CHECK_NO_LOG(!findDot, return 0);
230
231    FILE* fp = FileOpen(fileName, "r");
232    ENGINE_CHECK_NO_LOG(fp != nullptr, return 0);
233    int ret = fseek(fp, 0, SEEK_END);
234    ENGINE_CHECK_NO_LOG(ret == 0, fclose(fp);
235        return 0);
236    size_t length = (size_t)ftell(fp);
237    ret = fclose(fp);
238    ENGINE_CHECK_NO_LOG(ret == 0, return 0);
239    return length;
240}
241
242bool DownloadThread::DealAbnormal(uint32_t percent)
243{
244    bool dealResult = false;
245    if (isNoNet_ || isCancel_) {
246        ENGINE_LOGI("No network or user cancel");
247        downloadProgress_.endReason = isNoNet_ ? std::to_string(CAST_INT(DownloadEndReason::NET_NOT_AVAILIABLE)) :
248            std::to_string(CAST_INT(DownloadEndReason::CANCEL));
249        downloadProgress_.percent = percent;
250        downloadProgress_.status = isNoNet_ ? UpgradeStatus::DOWNLOAD_FAIL : UpgradeStatus::DOWNLOAD_CANCEL;
251        if (isCancel_) {
252            isCancel_ = false;
253        }
254        dealResult = true;
255        if (callback_ != nullptr) {
256            callback_(serverUrl_, downloadFileName_, downloadProgress_);
257        }
258    }
259    return dealResult;
260}
261
262FILE* DownloadThread::FileOpen(const std::string &fileName, const std::string &mode)
263{
264    if (fileName.empty() || fileName.size() > PATH_MAX) {
265        ENGINE_LOGI("DownloadThread file is empty or exceed path_max");
266        return nullptr;
267    }
268    std::string fileDir = fileName;
269    auto pos = fileDir.find_last_of("/");
270    if (pos != std::string::npos) {
271        fileDir.erase(pos + 1);
272    } else {
273        ENGINE_LOGI("DownloadThread file %{public}s, mode: %{public}s", fileName.c_str(), mode.c_str());
274        return nullptr;
275    }
276
277    char *path = realpath(fileDir.c_str(), NULL);
278    if (path == NULL) {
279        ENGINE_LOGI("DownloadThread file %{public}s, mode: %{public}s", fileName.c_str(), mode.c_str());
280        return nullptr;
281    }
282    free(path);
283    FILE* fp = fopen(fileName.c_str(), mode.c_str());
284    return fp;
285}
286} // namespace UpdateEngine
287} // namespace OHOS
288