RUDownloader.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. //
  2. // RUDownloader.cpp
  3. // red_utils
  4. //
  5. // Created by RedInfinity on 2023/12/6.
  6. //
  7. #include "RUDownloader.hpp"
  8. ///C++标准库
  9. #include <iomanip>
  10. #include <sstream>
  11. #include <iostream>
  12. NS_RU_BEGIN
  13. ///下载器日志
  14. #if CC_TARGET_PLATFORM == CC_PLATFORM_MAC
  15. #define REDUTILS_DOWNLOADER_LOG(format, ...) cocos2d::log("【RUDownloader】" format, ##__VA_ARGS__)
  16. #else
  17. #define REDUTILS_DOWNLOADER_LOG(format, ...) do {} while (0)
  18. #endif
  19. ///调用下载器委托的指定方法
  20. #define INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(delegate, method, ...) do { if (delegate) { (delegate)->method(__VA_ARGS__); } } while (0)
  21. Downloader* Downloader::create(const DownloaderCreateData& data, DownloaderDelegate* delegate) {
  22. Downloader* ret = new Downloader();
  23. ret->_init(data, delegate);
  24. return ret;
  25. }
  26. void Downloader::release() {
  27. delete _innerDownloader;
  28. delete this;
  29. }
  30. bool Downloader::downloadFile(const std::string& url, const std::string& saveDir) {
  31. DownloadRequestData requestData;
  32. requestData.url = url;
  33. requestData.saveDir = saveDir;
  34. return _appendDownloadRequest(requestData);
  35. }
  36. bool Downloader::downloadFileAndVerifyMd5(const std::string& url, const std::string& saveDir, const std::string& md5) {
  37. DownloadRequestData requestData;
  38. requestData.url = url;
  39. requestData.saveDir = saveDir;
  40. requestData.verify = true;
  41. requestData.md5 = md5;
  42. return _appendDownloadRequest(requestData);
  43. }
  44. void Downloader::_init(const DownloaderCreateData& data, DownloaderDelegate* delegate) {
  45. _id = data.id;
  46. _innerDownloader = _createInnerDownloader(data.maxDownloadCnt);
  47. _delegate = delegate;
  48. ///初始化内置的cocos2d下载器
  49. _initInnerDownloader();
  50. }
  51. cocos2d::network::Downloader* Downloader::_createInnerDownloader(const kMaxDownloadCnt& maxDownloadCnt) {
  52. cocos2d::network::DownloaderHints hints;
  53. hints.countOfMaxProcessingTasks = static_cast<int>(maxDownloadCnt);
  54. hints.timeoutInSeconds = 45;
  55. hints.tempFileNameSuffix = ".tmp";
  56. return new cocos2d::network::Downloader(hints);
  57. }
  58. void Downloader::_initInnerDownloader() {
  59. ///文件下载成功
  60. _innerDownloader->setOnFileTaskSuccess([this](const cocos2d::network::DownloadTask& task) {
  61. ///移除下载请求
  62. const std::string& decodeURL = _decodeURL(task.requestURL);
  63. DownloadRequestData requestData = _removeDownloadRequest(decodeURL);
  64. if (requestData.verify) {
  65. ///如果是需要验证的先验证下载完成的文件
  66. const std::string& saveFilePath = requestData.saveDir + _getFileNameByUrl(requestData.url);
  67. if (_verifyFileMd5(saveFilePath, requestData.md5)) {
  68. ///通知文件下载成功
  69. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileSuccess, requestData.url);
  70. REDUTILS_DOWNLOADER_LOG("下载成功并验证通过:%s", requestData.url.c_str());
  71. } else {
  72. ///通知文件下载失败
  73. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileFail, requestData.url, "file md5 verify fail");
  74. REDUTILS_DOWNLOADER_LOG("下载成功但验证失败:%s", requestData.url.c_str());
  75. }
  76. } else {
  77. ///通知文件下载成功
  78. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileSuccess, requestData.url);
  79. REDUTILS_DOWNLOADER_LOG("下载成功:%s", requestData.url.c_str());
  80. }
  81. ///完成下载
  82. _addDownloadedCnt();
  83. ///计算下载速度
  84. _updateDownloadSpeed(requestData.url);
  85. });
  86. ///文件下载失败
  87. _innerDownloader->setOnTaskError([this](const cocos2d::network::DownloadTask& task,
  88. int errorCode,
  89. int errorCodeInternal,
  90. const std::string& errorStr) {
  91. ///移除下载请求
  92. const std::string& decodeURL = _decodeURL(task.requestURL);
  93. DownloadRequestData requestData = _removeDownloadRequest(decodeURL);
  94. ///通知文件下载失败
  95. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileFail, requestData.url, errorStr);
  96. ///完成下载
  97. _addDownloadedCnt();
  98. ///计算下载速度
  99. _updateDownloadSpeed(requestData.url);
  100. REDUTILS_DOWNLOADER_LOG("下载失败:%s", requestData.url.c_str());
  101. });
  102. ///文件下载进行中
  103. _innerDownloader->setOnTaskProgress([this](const cocos2d::network::DownloadTask& task,
  104. int64_t bytesReceived,
  105. int64_t totalBytesReceived,
  106. int64_t totalBytesExpected) {
  107. const float& percent = static_cast<float>(totalBytesReceived * 100) / static_cast<float>(totalBytesExpected);
  108. ///通知文件下载百分比
  109. const std::string& decodeURL = _decodeURL(task.requestURL);
  110. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloadFileProgress, decodeURL, static_cast<int>(percent));
  111. ///因为cocos2d的下载没有开始下载的生命周期函数,所以需要在第一次有进度时记录下
  112. _recordDownloadStartTime(decodeURL);
  113. _updateDownloadedByte(decodeURL, totalBytesReceived);
  114. });
  115. }
  116. std::string Downloader::_encodeURL(const std::string& url) {
  117. std::ostringstream ret;
  118. ret.fill('0');
  119. ret << std::hex;
  120. for (char c : url) {
  121. if (0 <= c && c <= 127) {
  122. ret << c;
  123. } else {
  124. ret << std::uppercase;
  125. ret << '%' << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c));
  126. ret << std::nouppercase;
  127. }
  128. }
  129. return ret.str();
  130. }
  131. std::string Downloader::_decodeURL(const std::string& url) {
  132. std::ostringstream ret;
  133. ret.fill('0');
  134. ret << std::hex;
  135. char c = '\0';
  136. std::istringstream input(url);
  137. input.fill('0');
  138. while (input.get(c)) {
  139. if (c == '%') {
  140. int hexValue = 0;
  141. char c1 = '\0';
  142. char c2 = '\0';
  143. input.get(c1);
  144. input.get(c2);
  145. std::istringstream hexStream(std::string("0x") + c1 + c2);
  146. hexStream >> std::hex >> hexValue;
  147. ret << static_cast<char>(hexValue);
  148. } else {
  149. ret << c;
  150. }
  151. }
  152. return ret.str();
  153. }
  154. std::string Downloader::_getFileNameByUrl(const std::string& url) {
  155. return url.substr(url.rfind("/") + 1, url.size());
  156. }
  157. bool Downloader::_appendDownloadRequest(const DownloadRequestData& requestData) {
  158. ///请求的url地址为空
  159. if (requestData.url.empty()) {
  160. return false;
  161. }
  162. ///保存目录不存在
  163. if (cocos2d::FileUtils::getInstance()->isDirectoryExist(requestData.saveDir) == false) {
  164. return false;
  165. }
  166. ///下载请求已经存在
  167. if (_requestDatas.find(requestData.url) != _requestDatas.end()) {
  168. return false;
  169. }
  170. _requestDatas.emplace(requestData.url, requestData);
  171. _addTotalDownloadCnt();
  172. ///创建下载文件任务
  173. const std::string& saveFilePath = requestData.saveDir + _getFileNameByUrl(requestData.url);
  174. ///清理下载缓存
  175. _clearDownloadCache(saveFilePath);
  176. _innerDownloader->createDownloadFileTask(_encodeURL(requestData.url), saveFilePath, requestData.md5);
  177. return true;
  178. }
  179. DownloadRequestData Downloader::_removeDownloadRequest(const std::string& url) {
  180. DownloadRequestData ret = _requestDatas.at(url);
  181. _requestDatas.erase(url);
  182. return ret;
  183. }
  184. void Downloader::_addTotalDownloadCnt() {
  185. _totalDownloadCnt += 1;
  186. ///通知下载器下载百分比
  187. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderPercent, _getDownloadPercent());
  188. ///通知下载器下载进度
  189. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderProgress, _downloadedCnt, _totalDownloadCnt);
  190. }
  191. void Downloader::_addDownloadedCnt() {
  192. _downloadedCnt += 1;
  193. ///通知下载器下载百分比
  194. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderPercent, _getDownloadPercent());
  195. ///通知下载器下载进度
  196. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderProgress, _downloadedCnt, _totalDownloadCnt);
  197. }
  198. float Downloader::_getDownloadPercent() {
  199. return static_cast<float>(_downloadedCnt * 100) / static_cast<float>(_totalDownloadCnt);
  200. }
  201. bool Downloader::_verifyFileMd5(const std::string& saveFilePath, const std::string& md5) {
  202. const std::string& fileMd5 = cocos2d::utils::getFileMD5Hash(saveFilePath);
  203. if (fileMd5 == md5) {
  204. return true;
  205. }
  206. return false;
  207. }
  208. void Downloader::_recordDownloadStartTime(const std::string& url) {
  209. if (_downloadTimeClocks.find(url) == _downloadTimeClocks.end()) {
  210. DownloadTimeClock st = std::chrono::high_resolution_clock::now();
  211. _downloadTimeClocks.emplace(url, st);
  212. }
  213. }
  214. void Downloader::_updateDownloadedByte(const std::string& url, const int64_t& downloadedByte) {
  215. if (_downloadedBytes.find(url) == _downloadedBytes.end()) {
  216. _downloadedBytes.emplace(url, 0);
  217. }
  218. _downloadedBytes[url] = downloadedByte;
  219. }
  220. void Downloader::_updateDownloadSpeed(const std::string& url) {
  221. ///计算当前url的下载耗时
  222. double downloadTime = 0.0f;
  223. auto iter1 = _downloadTimeClocks.find(url);
  224. if (iter1 != _downloadTimeClocks.end()) {
  225. DownloadTimeClock et = std::chrono::high_resolution_clock::now();
  226. DownloadTimeClock st = iter1->second;
  227. _downloadTimeClocks.erase(url);
  228. downloadTime = std::chrono::duration_cast<std::chrono::milliseconds>(et - st).count() / 1000.0f;
  229. }
  230. ///计算当前url的下载速度
  231. int64_t downloadedByte = 0;
  232. auto iter2 = _downloadedBytes.find(url);
  233. if (iter2 != _downloadedBytes.end()) {
  234. downloadedByte = iter2->second;
  235. _downloadedBytes.erase(url);
  236. }
  237. int downloadSpeed = 0.0f;
  238. if (downloadTime != 0) {
  239. downloadSpeed = downloadedByte / downloadTime;
  240. }
  241. ///取上次下载速度平均下,不然会波动很大
  242. _downloadSpeed = (_downloadSpeed + downloadSpeed) / 2;
  243. ///通知下载器下载速度
  244. INVOKE_REDUTILS_DOWNLOADER_DELEGATE_METHOD(_delegate, onDownloaderSpeed, _downloadSpeed);
  245. }
  246. void Downloader::_clearDownloadCache(const std::string& zipFilePath) {
  247. ///zip文件
  248. if (cocos2d::FileUtils::getInstance()->isFileExist(zipFilePath)) {
  249. cocos2d::FileUtils::getInstance()->removeFile(zipFilePath);
  250. }
  251. ///zip临时文件
  252. const std::string& zipTmpFilePath = zipFilePath + ".tmp";
  253. if (cocos2d::FileUtils::getInstance()->isFileExist(zipTmpFilePath)) {
  254. cocos2d::FileUtils::getInstance()->removeFile(zipTmpFilePath);
  255. }
  256. ///解压后资源文件夹
  257. const std::string& uncompressFolderPath = zipFilePath.substr(0, zipFilePath.rfind("."));
  258. if (cocos2d::FileUtils::getInstance()->isDirectoryExist(uncompressFolderPath)) {
  259. cocos2d::FileUtils::getInstance()->removeDirectory(uncompressFolderPath);
  260. }
  261. }
  262. NS_RU_END