// // RUDownloader.cpp // red_utils // // Created by RedInfinity on 2023/12/6. // #include "RUDownloader.hpp" ///C++标准库 #include #include #include NS_RU_BEGIN ///下载器日志 #if CC_TARGET_PLATFORM == CC_PLATFORM_MAC #define REDUTILS_DOWNLOADER_LOG(format, ...) cocos2d::log("【RUDownloader】" format, ##__VA_ARGS__) #else #define REDUTILS_DOWNLOADER_LOG(format, ...) do {} while (0) #endif ///调用下载器委托的指定方法 #define INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(delegate, method, ...) do { if (delegate) { (delegate)->method(__VA_ARGS__); } } while (0) Downloader* Downloader::create(const DownloaderCreateData& data, DownloaderDelegate* delegate) { Downloader* ret = new Downloader(); ret->_init(data, delegate); return ret; } void Downloader::release() { delete _innerDownloader; delete this; } bool Downloader::downloadFile(const std::string& url, const std::string& saveDir) { DownloadRequestData requestData; requestData.url = url; requestData.saveDir = saveDir; return _appendDownloadRequest(requestData); } bool Downloader::downloadFileAndVerifyMd5(const std::string& url, const std::string& saveDir, const std::string& md5) { DownloadRequestData requestData; requestData.url = url; requestData.saveDir = saveDir; requestData.verify = true; requestData.md5 = md5; return _appendDownloadRequest(requestData); } void Downloader::_init(const DownloaderCreateData& data, DownloaderDelegate* delegate) { _id = data.id; _innerDownloader = _createInnerDownloader(data.maxDownloadCnt); _delegate = delegate; ///初始化内置的cocos2d下载器 _initInnerDownloader(); } cocos2d::network::Downloader* Downloader::_createInnerDownloader(const kMaxDownloadCnt& maxDownloadCnt) { cocos2d::network::DownloaderHints hints; hints.countOfMaxProcessingTasks = static_cast(maxDownloadCnt); hints.timeoutInSeconds = 45; hints.tempFileNameSuffix = ".tmp"; return new cocos2d::network::Downloader(hints); } void Downloader::_initInnerDownloader() { ///文件下载成功 _innerDownloader->setOnFileTaskSuccess([this](const cocos2d::network::DownloadTask& task) { ///移除下载请求 const std::string& decodeURL = _decodeURL(task.requestURL); DownloadRequestData requestData = _removeDownloadRequest(decodeURL); if (requestData.verify) { ///如果是需要验证的先验证下载完成的文件 const std::string& saveFilePath = requestData.saveDir + _getFileNameByUrl(requestData.url); if (_verifyFileMd5(saveFilePath, requestData.md5)) { ///通知文件下载成功 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileSuccess, requestData.url); REDUTILS_DOWNLOADER_LOG("下载成功并验证通过:%s", requestData.url.c_str()); } else { ///通知文件下载失败 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileFail, requestData.url, "file md5 verify fail"); REDUTILS_DOWNLOADER_LOG("下载成功但验证失败:%s", requestData.url.c_str()); } } else { ///通知文件下载成功 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileSuccess, requestData.url); REDUTILS_DOWNLOADER_LOG("下载成功:%s", requestData.url.c_str()); } ///完成下载 _addDownloadedCnt(); ///计算下载速度 _updateDownloadSpeed(requestData.url); }); ///文件下载失败 _innerDownloader->setOnTaskError([this](const cocos2d::network::DownloadTask& task, int errorCode, int errorCodeInternal, const std::string& errorStr) { ///移除下载请求 const std::string& decodeURL = _decodeURL(task.requestURL); DownloadRequestData requestData = _removeDownloadRequest(decodeURL); ///通知文件下载失败 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileFail, requestData.url, errorStr); ///完成下载 _addDownloadedCnt(); ///计算下载速度 _updateDownloadSpeed(requestData.url); REDUTILS_DOWNLOADER_LOG("下载失败:%s", requestData.url.c_str()); }); ///文件下载进行中 _innerDownloader->setOnTaskProgress([this](const cocos2d::network::DownloadTask& task, int64_t bytesReceived, int64_t totalBytesReceived, int64_t totalBytesExpected) { const float& percent = static_cast(totalBytesReceived * 100) / static_cast(totalBytesExpected); ///通知文件下载百分比 const std::string& decodeURL = _decodeURL(task.requestURL); INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileProgress, decodeURL, static_cast(percent)); ///因为cocos2d的下载没有开始下载的生命周期函数,所以需要在第一次有进度时记录下 _recordDownloadStartTime(decodeURL); _updateDownloadedByte(decodeURL, totalBytesReceived); }); } std::string Downloader::_encodeURL(const std::string& url) { std::ostringstream ret; ret.fill('0'); ret << std::hex; for (char c : url) { if (0 <= c && c <= 127) { ret << c; } else { ret << std::uppercase; ret << '%' << std::setw(2) << static_cast(static_cast(c)); ret << std::nouppercase; } } return ret.str(); } std::string Downloader::_decodeURL(const std::string& url) { std::ostringstream ret; ret.fill('0'); ret << std::hex; char c = '\0'; std::istringstream input(url); input.fill('0'); while (input.get(c)) { if (c == '%') { int hexValue = 0; char c1 = '\0'; char c2 = '\0'; input.get(c1); input.get(c2); std::istringstream hexStream(std::string("0x") + c1 + c2); hexStream >> std::hex >> hexValue; ret << static_cast(hexValue); } else { ret << c; } } return ret.str(); } std::string Downloader::_getFileNameByUrl(const std::string& url) { return url.substr(url.rfind("/") + 1, url.size()); } bool Downloader::_appendDownloadRequest(const DownloadRequestData& requestData) { ///请求的url地址为空 if (requestData.url.empty()) { return false; } ///保存目录不存在 if (cocos2d::FileUtils::getInstance()->isDirectoryExist(requestData.saveDir) == false) { return false; } ///下载请求已经存在 if (_requestDatas.find(requestData.url) != _requestDatas.end()) { return false; } _requestDatas.emplace(requestData.url, requestData); _addTotalDownloadCnt(); ///创建下载文件任务 const std::string& saveFilePath = requestData.saveDir + _getFileNameByUrl(requestData.url); ///清理下载缓存 _clearDownloadCache(saveFilePath); _innerDownloader->createDownloadFileTask(_encodeURL(requestData.url), saveFilePath, requestData.md5); return true; } DownloadRequestData Downloader::_removeDownloadRequest(const std::string& url) { DownloadRequestData ret = _requestDatas.at(url); _requestDatas.erase(url); return ret; } void Downloader::_addTotalDownloadCnt() { _totalDownloadCnt += 1; ///通知下载器下载百分比 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderPercent, _getDownloadPercent()); ///通知下载器下载进度 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderProgress, _downloadedCnt, _totalDownloadCnt); } void Downloader::_addDownloadedCnt() { _downloadedCnt += 1; ///通知下载器下载百分比 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderPercent, _getDownloadPercent()); ///通知下载器下载进度 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderProgress, _downloadedCnt, _totalDownloadCnt); } float Downloader::_getDownloadPercent() { return static_cast(_downloadedCnt * 100) / static_cast(_totalDownloadCnt); } bool Downloader::_verifyFileMd5(const std::string& saveFilePath, const std::string& md5) { const std::string& fileMd5 = cocos2d::utils::getFileMD5Hash(saveFilePath); if (fileMd5 == md5) { return true; } return false; } void Downloader::_recordDownloadStartTime(const std::string& url) { if (_downloadTimeClocks.find(url) == _downloadTimeClocks.end()) { DownloadTimeClock st = std::chrono::high_resolution_clock::now(); _downloadTimeClocks.emplace(url, st); } } void Downloader::_updateDownloadedByte(const std::string& url, const int64_t& downloadedByte) { if (_downloadedBytes.find(url) == _downloadedBytes.end()) { _downloadedBytes.emplace(url, 0); } _downloadedBytes[url] = downloadedByte; } void Downloader::_updateDownloadSpeed(const std::string& url) { ///计算当前url的下载耗时 double downloadTime = 0.0f; auto iter1 = _downloadTimeClocks.find(url); if (iter1 != _downloadTimeClocks.end()) { DownloadTimeClock et = std::chrono::high_resolution_clock::now(); DownloadTimeClock st = iter1->second; _downloadTimeClocks.erase(url); downloadTime = std::chrono::duration_cast(et - st).count() / 1000.0f; } ///计算当前url的下载速度 int64_t downloadedByte = 0; auto iter2 = _downloadedBytes.find(url); if (iter2 != _downloadedBytes.end()) { downloadedByte = iter2->second; _downloadedBytes.erase(url); } int downloadSpeed = 0.0f; if (downloadTime != 0) { downloadSpeed = downloadedByte / downloadTime; } ///取上次下载速度平均下,不然会波动很大 _downloadSpeed = (_downloadSpeed + downloadSpeed) / 2; ///通知下载器下载速度 INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderSpeed, _downloadSpeed); } void Downloader::_clearDownloadCache(const std::string& zipFilePath) { ///zip文件 if (cocos2d::FileUtils::getInstance()->isFileExist(zipFilePath)) { cocos2d::FileUtils::getInstance()->removeFile(zipFilePath); } ///zip临时文件 const std::string& zipTmpFilePath = zipFilePath + ".tmp"; if (cocos2d::FileUtils::getInstance()->isFileExist(zipTmpFilePath)) { cocos2d::FileUtils::getInstance()->removeFile(zipTmpFilePath); } ///解压后资源文件夹 const std::string& uncompressFolderPath = zipFilePath.substr(0, zipFilePath.rfind(".")); if (cocos2d::FileUtils::getInstance()->isDirectoryExist(uncompressFolderPath)) { cocos2d::FileUtils::getInstance()->removeDirectory(uncompressFolderPath); } } NS_RU_END