1
0

AssetsManager.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. /****************************************************************************
  2. Copyright (c) 2013 cocos2d-x.org
  3. http://www.cocos2d-x.org
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. ****************************************************************************/
  20. #include "AssetsManager.h"
  21. #include <thread>
  22. #include "base/CCDirector.h"
  23. #include "base/CCScheduler.h"
  24. #include "base/CCUserDefault.h"
  25. #include "network/CCDownloader.h"
  26. #include "platform/CCFileUtils.h"
  27. #ifdef MINIZIP_FROM_SYSTEM
  28. #include <minizip/unzip.h>
  29. #else // from our embedded sources
  30. #include "unzip.h"
  31. #endif
  32. NS_CC_EXT_BEGIN;
  33. using namespace std;
  34. using namespace cocos2d;
  35. using namespace cocos2d::network;
  36. #define KEY_OF_VERSION "current-version-code"
  37. #define KEY_OF_DOWNLOADED_VERSION "downloaded-version-code"
  38. #define TEMP_PACKAGE_FILE_NAME "cocos2dx-update-temp-package.zip"
  39. #define BUFFER_SIZE 8192
  40. #define MAX_FILENAME 512
  41. // Implementation of AssetsManager
  42. AssetsManager::AssetsManager(const char* packageUrl/* =nullptr */, const char* versionFileUrl/* =nullptr */, const char* storagePath/* =nullptr */)
  43. : _storagePath(storagePath ? storagePath : "")
  44. , _version("")
  45. , _packageUrl(packageUrl ? packageUrl : "")
  46. , _versionFileUrl(versionFileUrl ? versionFileUrl : "")
  47. , _downloadedVersion("")
  48. , _downloader(new Downloader())
  49. , _connectionTimeout(0)
  50. , _delegate(nullptr)
  51. , _isDownloading(false)
  52. , _shouldDeleteDelegateWhenExit(false)
  53. {
  54. checkStoragePath();
  55. // convert downloader error code to AssetsManager::ErrorCode
  56. _downloader->onTaskError = [this](const DownloadTask& /*task*/,
  57. int errorCode,
  58. int /*errorCodeInternal*/,
  59. const std::string& /*errorStr*/)
  60. {
  61. _isDownloading = false;
  62. if (nullptr == _delegate)
  63. {
  64. return;
  65. }
  66. auto err = (DownloadTask::ERROR_FILE_OP_FAILED == errorCode) ? ErrorCode::CREATE_FILE : ErrorCode::NETWORK;
  67. _delegate->onError(err);
  68. };
  69. // progress callback
  70. _downloader->onTaskProgress = [this](const DownloadTask& task,
  71. int64_t /*bytesReceived*/,
  72. int64_t totalBytesReceived,
  73. int64_t totalBytesExpected)
  74. {
  75. if(FileUtils::getInstance()->getFileExtension(task.requestURL) != ".zip")
  76. {
  77. // get version progress don't report
  78. return;
  79. }
  80. if (nullptr == _delegate)
  81. {
  82. return;
  83. }
  84. int percent = totalBytesExpected ? int(totalBytesReceived * 100 / totalBytesExpected) : 0;
  85. _delegate->onProgress(percent);
  86. CCLOG("downloading... %d%%", percent);
  87. };
  88. // get version from version file when get data success
  89. _downloader->onDataTaskSuccess = [this](const DownloadTask& /*task*/,
  90. std::vector<unsigned char>& data)
  91. {
  92. // store version info to member _version
  93. const char *p = (char *)data.data();
  94. _version.insert(_version.end(), p, p + data.size());
  95. if (getVersion() == _version)
  96. {
  97. if (_delegate)
  98. {
  99. _delegate->onError(ErrorCode::NO_NEW_VERSION);
  100. }
  101. CCLOG("there is not new version");
  102. // Set resource search path.
  103. setSearchPath();
  104. _isDownloading = false;
  105. return;
  106. }
  107. // start download new version assets
  108. // 1. Urls of package and version should be valid;
  109. // 2. Package should be a zip file.
  110. if (_versionFileUrl.empty()
  111. || _packageUrl.empty()
  112. || FileUtils::getInstance()->getFileExtension(_packageUrl) != ".zip"
  113. )
  114. {
  115. CCLOG("no version file url, or no package url, or the package is not a zip file");
  116. _isDownloading = false;
  117. return;
  118. }
  119. // Is package already downloaded?
  120. _downloadedVersion = UserDefault::getInstance()->getStringForKey(keyOfDownloadedVersion().c_str());
  121. if (_downloadedVersion == _version)
  122. {
  123. downloadAndUncompress();
  124. return;
  125. }
  126. // start download;
  127. const string outFileName = _storagePath + TEMP_PACKAGE_FILE_NAME;
  128. _downloader->createDownloadFileTask(_packageUrl, outFileName);
  129. };
  130. // after download package, do uncompress operation
  131. _downloader->onFileTaskSuccess = [this](const DownloadTask& /*task*/)
  132. {
  133. downloadAndUncompress();
  134. };
  135. }
  136. AssetsManager::~AssetsManager()
  137. {
  138. if (_shouldDeleteDelegateWhenExit)
  139. {
  140. delete _delegate;
  141. }
  142. CC_SAFE_DELETE(_downloader);
  143. }
  144. void AssetsManager::checkStoragePath()
  145. {
  146. if (_storagePath.size() > 0 && _storagePath[_storagePath.size() - 1] != '/')
  147. {
  148. _storagePath.append("/");
  149. }
  150. }
  151. // Multiple key names
  152. static std::string keyWithHash( const char* prefix, const std::string& url )
  153. {
  154. char buf[256];
  155. sprintf(buf,"%s%zd",prefix,std::hash<std::string>()(url));
  156. return buf;
  157. }
  158. // hashed version
  159. std::string AssetsManager::keyOfVersion() const
  160. {
  161. return keyWithHash(KEY_OF_VERSION,_packageUrl);
  162. }
  163. // hashed version
  164. std::string AssetsManager::keyOfDownloadedVersion() const
  165. {
  166. return keyWithHash(KEY_OF_DOWNLOADED_VERSION,_packageUrl);
  167. }
  168. bool AssetsManager::checkUpdate()
  169. {
  170. if (_versionFileUrl.size() == 0 || _isDownloading) return false;
  171. // Clear _version before assign new value.
  172. _version.clear();
  173. _isDownloading = true;
  174. _downloader->createDownloadDataTask(_versionFileUrl);
  175. return true;
  176. }
  177. void AssetsManager::downloadAndUncompress()
  178. {
  179. std::thread([this]()
  180. {
  181. do
  182. {
  183. // Uncompress zip file.
  184. if (! uncompress())
  185. {
  186. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{
  187. UserDefault::getInstance()->setStringForKey(this->keyOfDownloadedVersion().c_str(),"");
  188. UserDefault::getInstance()->flush();
  189. if (this->_delegate)
  190. this->_delegate->onError(ErrorCode::UNCOMPRESS);
  191. });
  192. break;
  193. }
  194. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this] {
  195. // Record new version code.
  196. UserDefault::getInstance()->setStringForKey(this->keyOfVersion().c_str(), this->_version.c_str());
  197. // Unrecord downloaded version code.
  198. UserDefault::getInstance()->setStringForKey(this->keyOfDownloadedVersion().c_str(), "");
  199. UserDefault::getInstance()->flush();
  200. // Set resource search path.
  201. this->setSearchPath();
  202. // Delete unloaded zip file.
  203. string zipfileName = this->_storagePath + TEMP_PACKAGE_FILE_NAME;
  204. if (remove(zipfileName.c_str()) != 0)
  205. {
  206. CCLOG("can not remove downloaded zip file %s", zipfileName.c_str());
  207. }
  208. if (this->_delegate) this->_delegate->onSuccess();
  209. });
  210. } while (0);
  211. _isDownloading = false;
  212. }).detach();
  213. }
  214. void AssetsManager::update()
  215. {
  216. // all operation in checkUpdate, nothing need to do
  217. // keep this function for compatibility
  218. }
  219. bool AssetsManager::uncompress()
  220. {
  221. // Open the zip file
  222. string outFileName = _storagePath + TEMP_PACKAGE_FILE_NAME;
  223. unzFile zipfile = unzOpen(FileUtils::getInstance()->getSuitableFOpen(outFileName).c_str());
  224. if (! zipfile)
  225. {
  226. CCLOG("can not open downloaded zip file %s", outFileName.c_str());
  227. return false;
  228. }
  229. // Get info about the zip file
  230. unz_global_info global_info;
  231. if (unzGetGlobalInfo(zipfile, &global_info) != UNZ_OK)
  232. {
  233. CCLOG("can not read file global info of %s", outFileName.c_str());
  234. unzClose(zipfile);
  235. return false;
  236. }
  237. // Buffer to hold data read from the zip file
  238. char readBuffer[BUFFER_SIZE];
  239. CCLOG("start uncompressing");
  240. // Loop to extract all files.
  241. uLong i;
  242. for (i = 0; i < global_info.number_entry; ++i)
  243. {
  244. // Get info about current file.
  245. unz_file_info fileInfo;
  246. char fileName[MAX_FILENAME];
  247. if (unzGetCurrentFileInfo(zipfile,
  248. &fileInfo,
  249. fileName,
  250. MAX_FILENAME,
  251. nullptr,
  252. 0,
  253. nullptr,
  254. 0) != UNZ_OK)
  255. {
  256. CCLOG("can not read file info");
  257. unzClose(zipfile);
  258. return false;
  259. }
  260. const string fullPath = _storagePath + fileName;
  261. // Check if this entry is a directory or a file.
  262. const size_t filenameLength = strlen(fileName);
  263. if (fileName[filenameLength-1] == '/')
  264. {
  265. // Entry is a directory, so create it.
  266. // If the directory exists, it will failed silently.
  267. if (!FileUtils::getInstance()->createDirectory(fullPath))
  268. {
  269. CCLOG("can not create directory %s", fullPath.c_str());
  270. unzClose(zipfile);
  271. return false;
  272. }
  273. }
  274. else
  275. {
  276. //There are not directory entry in some case.
  277. //So we need to test whether the file directory exists when uncompressing file entry
  278. //, if does not exist then create directory
  279. const string fileNameStr(fileName);
  280. size_t startIndex=0;
  281. size_t index=fileNameStr.find("/",startIndex);
  282. while(index != std::string::npos)
  283. {
  284. const string dir=_storagePath+fileNameStr.substr(0,index);
  285. FILE *out = fopen(FileUtils::getInstance()->getSuitableFOpen(dir).c_str(), "r");
  286. if(!out)
  287. {
  288. if (!FileUtils::getInstance()->createDirectory(dir))
  289. {
  290. CCLOG("can not create directory %s", dir.c_str());
  291. unzClose(zipfile);
  292. return false;
  293. }
  294. else
  295. {
  296. CCLOG("create directory %s",dir.c_str());
  297. }
  298. }
  299. else
  300. {
  301. fclose(out);
  302. }
  303. startIndex=index+1;
  304. index=fileNameStr.find("/",startIndex);
  305. }
  306. // Entry is a file, so extract it.
  307. // Open current file.
  308. if (unzOpenCurrentFile(zipfile) != UNZ_OK)
  309. {
  310. CCLOG("can not open file %s", fileName);
  311. unzClose(zipfile);
  312. return false;
  313. }
  314. // Create a file to store current file.
  315. FILE *out = fopen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), "wb");
  316. if (! out)
  317. {
  318. CCLOG("can not open destination file %s", fullPath.c_str());
  319. unzCloseCurrentFile(zipfile);
  320. unzClose(zipfile);
  321. return false;
  322. }
  323. // Write current file content to destinate file.
  324. int error = UNZ_OK;
  325. do
  326. {
  327. error = unzReadCurrentFile(zipfile, readBuffer, BUFFER_SIZE);
  328. if (error < 0)
  329. {
  330. CCLOG("can not read zip file %s, error code is %d", fileName, error);
  331. unzCloseCurrentFile(zipfile);
  332. unzClose(zipfile);
  333. return false;
  334. }
  335. if (error > 0)
  336. {
  337. fwrite(readBuffer, error, 1, out);
  338. }
  339. } while(error > 0);
  340. fclose(out);
  341. }
  342. unzCloseCurrentFile(zipfile);
  343. // Goto next entry listed in the zip file.
  344. if ((i+1) < global_info.number_entry)
  345. {
  346. if (unzGoToNextFile(zipfile) != UNZ_OK)
  347. {
  348. CCLOG("can not read next file");
  349. unzClose(zipfile);
  350. return false;
  351. }
  352. }
  353. }
  354. CCLOG("end uncompressing");
  355. unzClose(zipfile);
  356. return true;
  357. }
  358. void AssetsManager::setSearchPath()
  359. {
  360. vector<string> searchPaths = FileUtils::getInstance()->getSearchPaths();
  361. vector<string>::iterator iter = searchPaths.begin();
  362. searchPaths.insert(iter, _storagePath);
  363. FileUtils::getInstance()->setSearchPaths(searchPaths);
  364. }
  365. const char* AssetsManager::getPackageUrl() const
  366. {
  367. return _packageUrl.c_str();
  368. }
  369. void AssetsManager::setPackageUrl(const char *packageUrl)
  370. {
  371. _packageUrl = packageUrl;
  372. }
  373. const char* AssetsManager::getStoragePath() const
  374. {
  375. return _storagePath.c_str();
  376. }
  377. void AssetsManager::setStoragePath(const char *storagePath)
  378. {
  379. _storagePath = storagePath;
  380. checkStoragePath();
  381. }
  382. const char* AssetsManager::getVersionFileUrl() const
  383. {
  384. return _versionFileUrl.c_str();
  385. }
  386. void AssetsManager::setVersionFileUrl(const char *versionFileUrl)
  387. {
  388. _versionFileUrl = versionFileUrl;
  389. }
  390. string AssetsManager::getVersion()
  391. {
  392. return UserDefault::getInstance()->getStringForKey(keyOfVersion().c_str());
  393. }
  394. void AssetsManager::deleteVersion()
  395. {
  396. UserDefault::getInstance()->setStringForKey(keyOfVersion().c_str(), "");
  397. }
  398. void AssetsManager::setDelegate(AssetsManagerDelegateProtocol *delegate)
  399. {
  400. _delegate = delegate;
  401. }
  402. void AssetsManager::setConnectionTimeout(unsigned int timeout)
  403. {
  404. _connectionTimeout = timeout;
  405. }
  406. unsigned int AssetsManager::getConnectionTimeout()
  407. {
  408. return _connectionTimeout;
  409. }
  410. AssetsManager* AssetsManager::create(const char* packageUrl, const char* versionFileUrl, const char* storagePath, ErrorCallback errorCallback, ProgressCallback progressCallback, SuccessCallback successCallback )
  411. {
  412. class DelegateProtocolImpl : public AssetsManagerDelegateProtocol
  413. {
  414. public :
  415. DelegateProtocolImpl(ErrorCallback& aErrorCallback, ProgressCallback& aProgressCallback, SuccessCallback& aSuccessCallback)
  416. : errorCallback(aErrorCallback), progressCallback(aProgressCallback), successCallback(aSuccessCallback)
  417. {}
  418. virtual void onError(AssetsManager::ErrorCode errorCode) { errorCallback(int(errorCode)); }
  419. virtual void onProgress(int percent) { progressCallback(percent); }
  420. virtual void onSuccess() { successCallback(); }
  421. private :
  422. ErrorCallback errorCallback;
  423. ProgressCallback progressCallback;
  424. SuccessCallback successCallback;
  425. };
  426. auto* manager = new (std::nothrow) AssetsManager(packageUrl,versionFileUrl,storagePath);
  427. auto* delegate = new (std::nothrow) DelegateProtocolImpl(errorCallback,progressCallback,successCallback);
  428. manager->setDelegate(delegate);
  429. manager->_shouldDeleteDelegateWhenExit = true;
  430. manager->autorelease();
  431. return manager;
  432. }
  433. NS_CC_EXT_END;