cocos2d热更新代码分析
#include "AssetsManagerEx.h"
#include "CCEventListenerAssetsManagerEx.h"
#include "deprecated/CCString.h"
#include "base/CCDirector.h"
#include <curl/curl.h>
#include <curl/easy.h>
#include <stdio.h>
#ifdef MINIZIP_FROM_SYSTEM
#include <minizip/unzip.h>
#else
#include "unzip.h"
#endif
using namespace cocos2d;
using namespace std;
NS_CC_EXT_BEGIN
#define VERSION_FILENAME "version.manifest"
#define TEMP_MANIFEST_FILENAME "project.manifest.temp"
#define MANIFEST_FILENAME "project.manifest"
#define BUFFER_SIZE 8192
#define MAX_FILENAME 512
#define DEFAULT_CONNECTION_TIMEOUT 8
const std::string AssetsManagerEx::VERSION_ID = "@version";
const std::string AssetsManagerEx::MANIFEST_ID = "@manifest";
const std::string AssetsManagerEx::BATCH_UPDATE_ID = "@batch_update";
AssetsManagerEx::AssetsManagerEx(const std::string& manifestUrl,const std::string& storagePath)
: _updateState(State::UNCHECKED),_assets(nullptr),_storagePath(""),_cacheVersionPath(""),_cacheManifestPath(""),_tempManifestPath(""),_manifestUrl(manifestUrl),_localManifest(nullptr),_tempManifest(nullptr),_remoteManifest(nullptr),_waitToUpdate(false),_percent(0),_percentByFile(0),_totalToDownload(0),_totalWaitToDownload(0),_inited(false)
{
_eventDispatcher = Director::getInstance()->getEventDispatcher();
std::string pointer = StringUtils::format("%p",this);
_eventName = EventListenerAssetsManagerEx::LISTENER_ID + pointer;
_fileUtils = FileUtils::getInstance();
_updateState = State::UNCHECKED;
_downloader = std::make_shared<Downloader>();
_downloader->setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT);
_downloader->_onError = std::bind(&AssetsManagerEx::onError,this,std::placeholders::_1);
_downloader->_onProgress = std::bind(&AssetsManagerEx::onProgress,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3,std::placeholders::_4);
_downloader->_onSuccess = std::bind(&AssetsManagerEx::onSuccess,std::placeholders::_3);
setStoragePath(storagePath);
_cacheVersionPath = _storagePath + VERSION_FILENAME;
_cacheManifestPath = _storagePath + MANIFEST_FILENAME;
_tempManifestPath = _storagePath + TEMP_MANIFEST_FILENAME;
initManifests(manifestUrl);
}
AssetsManagerEx::~AssetsManagerEx()
{
_downloader->_onError = nullptr;
_downloader->_onSuccess = nullptr;
_downloader->_onProgress = nullptr;
CC_SAFE_RELEASE(_localManifest);
if (_tempManifest != _localManifest && _tempManifest != _remoteManifest)
CC_SAFE_RELEASE(_tempManifest);
CC_SAFE_RELEASE(_remoteManifest);
}
AssetsManagerEx* AssetsManagerEx::create(const std::string& manifestUrl,const std::string& storagePath)
{
AssetsManagerEx* ret = new (std::nothrow) AssetsManagerEx(manifestUrl,storagePath);
if (ret)
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
void AssetsManagerEx::initManifests(const std::string& manifestUrl)
{
_inited = true;
_localManifest = new (std::nothrow) Manifest();
if (_localManifest)
{
loadLocalManifest(manifestUrl);
_tempManifest = new (std::nothrow) Manifest();
if (_tempManifest)
{
_tempManifest->parse(_tempManifestPath);
if (!_tempManifest->isLoaded())
_fileUtils->removeFile(_tempManifestPath);
}
else
{
_inited = false;
}
_remoteManifest = new (std::nothrow) Manifest();
if (!_remoteManifest)
{
_inited = false;
}
}
else
{
_inited = false;
}
if (!_inited)
{
CC_SAFE_DELETE(_localManifest);
CC_SAFE_DELETE(_tempManifest);
CC_SAFE_DELETE(_remoteManifest);
}
}
void AssetsManagerEx::prepareLocalManifest()
{
_assets = &(_localManifest->getAssets());
_localManifest->prependSearchPaths();
}
void AssetsManagerEx::loadLocalManifest(const std::string& manifestUrl)
{
Manifest *cachedManifest = nullptr;
if (_fileUtils->isFileExist(_cacheManifestPath))
{
cachedManifest = new (std::nothrow) Manifest();
if (cachedManifest) {
cachedManifest->parse(_cacheManifestPath);
if (!cachedManifest->isLoaded())
{
_fileUtils->removeFile(_cacheManifestPath);
CC_SAFE_RELEASE(cachedManifest);
cachedManifest = nullptr;
}
}
}
_localManifest->parse(_manifestUrl);
if (_localManifest->isLoaded())
{
if (cachedManifest) {
if (strcmp(_localManifest->getVersion().c_str(),cachedManifest->getVersion().c_str()) > 0)
{
_fileUtils->removeDirectory(_storagePath);
_fileUtils->createDirectory(_storagePath);
CC_SAFE_RELEASE(cachedManifest);
}
else
{
CC_SAFE_RELEASE(_localManifest);
_localManifest = cachedManifest;
}
}
prepareLocalManifest();
}
if (!_localManifest->isLoaded())
{
CCLOG("AssetsManagerEx : No local manifest file found error.\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
}
}
std::string AssetsManagerEx::basename(const std::string& path) const
{
size_t found = path.find_last_of("/\\");
if (std::string::npos != found)
{
return path.substr(0,found);
}
else
{
return path;
}
}
std::string AssetsManagerEx::get(const std::string& key) const
{
auto it = _assets->find(key);
if (it != _assets->cend()) {
return _storagePath + it->second.path;
}
else return "";
}
const Manifest* AssetsManagerEx::getLocalManifest() const
{
return _localManifest;
}
const Manifest* AssetsManagerEx::getRemoteManifest() const
{
return _remoteManifest;
}
const std::string& AssetsManagerEx::getStoragePath() const
{
return _storagePath;
}
void AssetsManagerEx::setStoragePath(const std::string& storagePath)
{
_storagePath = storagePath;
adjustPath(_storagePath);
_fileUtils->createDirectory(_storagePath);
}
void AssetsManagerEx::adjustPath(std::string &path)
{
if (path.size() > 0 && path[path.size() - 1] != '/')
{
path.append("/");
}
}
bool AssetsManagerEx::decompress(const std::string &zip)
{
size_t pos = zip.find_last_of("/\\");
if (pos == std::string::npos)
{
CCLOG("AssetsManagerEx : no root path specified for zip file %s\n",zip.c_str());
return false;
}
const std::string rootPath = zip.substr(0,pos+1);
unzFile zipfile = unzOpen(zip.c_str());
if (! zipfile)
{
CCLOG("AssetsManagerEx : can not open downloaded zip file %s\n",zip.c_str());
return false;
}
unz_global_info global_info;
if (unzGetGlobalInfo(zipfile,&global_info) != UNZ_OK)
{
CCLOG("AssetsManagerEx : can not read file global info of %s\n",zip.c_str());
unzClose(zipfile);
return false;
}
char readBuffer[BUFFER_SIZE];
uLong i;
for (i = 0; i < global_info.number_entry; ++i)
{
unz_file_info fileInfo;
char fileName[MAX_FILENAME];
if (unzGetCurrentFileInfo(zipfile,&fileInfo,fileName,MAX_FILENAME,NULL,0,0) != UNZ_OK)
{
CCLOG("AssetsManagerEx : can not read compressed file info\n");
unzClose(zipfile);
return false;
}
const std::string fullPath = rootPath + fileName;
const size_t filenameLength = strlen(fileName);
if (fileName[filenameLength-1] == '/')
{
if ( !_fileUtils->createDirectory(basename(fullPath)) )
{
CCLOG("AssetsManagerEx : can not create directory %s\n",fullPath.c_str());
unzClose(zipfile);
return false;
}
}
else
{
if (unzOpenCurrentFile(zipfile) != UNZ_OK)
{
CCLOG("AssetsManagerEx : can not extract file %s\n",fileName);
unzClose(zipfile);
return false;
}
FILE *out = fopen(fullPath.c_str(),"wb");
if (!out)
{
CCLOG("AssetsManagerEx : can not create decompress destination file %s\n",fullPath.c_str());
unzCloseCurrentFile(zipfile);
unzClose(zipfile);
return false;
}
int error = UNZ_OK;
do
{
error = unzReadCurrentFile(zipfile,readBuffer,BUFFER_SIZE);
if (error < 0)
{
CCLOG("AssetsManagerEx : can not read zip file %s,error code is %d\n",error);
fclose(out);
unzCloseCurrentFile(zipfile);
unzClose(zipfile);
return false;
}
if (error > 0)
{
fwrite(readBuffer,error,1,out);
}
} while(error > 0);
fclose(out);
}
unzCloseCurrentFile(zipfile);
if ((i+1) < global_info.number_entry)
{
if (unzGoToNextFile(zipfile) != UNZ_OK)
{
CCLOG("AssetsManagerEx : can not read next file for decompressing\n");
unzClose(zipfile);
return false;
}
}
}
unzClose(zipfile);
return true;
}
void AssetsManagerEx::decompressDownloadedZip()
{
for (auto it = _compressedFiles.begin(); it != _compressedFiles.end(); ++it) {
std::string zipfile = *it;
if (!decompress(zipfile))
{
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DECOMPRESS,"","Unable to decompress file " + zipfile);
}
_fileUtils->removeFile(zipfile);
}
_compressedFiles.clear();
}
void AssetsManagerEx::dispatchUpdateEvent(EventAssetsManagerEx::EventCode code,const std::string &assetId,const std::string &message,int curle_code,int curlm_code)
{
EventAssetsManagerEx event(_eventName,code,_percent,_percentByFile,assetId,message,curle_code,curlm_code);
_eventDispatcher->dispatchEvent(&event);
}
AssetsManagerEx::State AssetsManagerEx::getState() const
{
return _updateState;
}
void AssetsManagerEx::downloadVersion()
{
if (_updateState > State::PREDOWNLOAD_VERSION)
return;
std::string versionUrl = _localManifest->getVersionFileUrl();
if (versionUrl.size() > 0)
{
_updateState = State::DOWNLOADING_VERSION;
_downloader->downloadAsync(versionUrl,_cacheVersionPath,VERSION_ID);
}
else
{
CCLOG("AssetsManagerEx : No version file found,step skipped\n");
_updateState = State::PREDOWNLOAD_MANIFEST;
downloadManifest();
}
}
void AssetsManagerEx::parseVersion()
{
if (_updateState != State::VERSION_LOADED)
return;
_remoteManifest->parseVersion(_cacheVersionPath);
if (!_remoteManifest->isVersionLoaded())
{
CCLOG("AssetsManagerEx : Fail to parse version file,step skipped\n");
_updateState = State::PREDOWNLOAD_MANIFEST;
downloadManifest();
}
else
{
if (_localManifest->versionEquals(_remoteManifest))
{
_updateState = State::UP_TO_DATE;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);
}
else
{
_updateState = State::NEED_UPDATE;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);
if (_waitToUpdate)
{
_updateState = State::PREDOWNLOAD_MANIFEST;
downloadManifest();
}
}
}
}
void AssetsManagerEx::downloadManifest()
{
if (_updateState != State::PREDOWNLOAD_MANIFEST)
return;
std::string manifestUrl = _localManifest->getManifestFileUrl();
if (manifestUrl.size() > 0)
{
_updateState = State::DOWNLOADING_MANIFEST;
_downloader->downloadAsync(manifestUrl,_tempManifestPath,MANIFEST_ID);
}
else
{
CCLOG("AssetsManagerEx : No manifest file found,check update Failed\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST);
_updateState = State::UNCHECKED;
}
}
void AssetsManagerEx::parseManifest()
{
if (_updateState != State::MANIFEST_LOADED)
return;
_remoteManifest->parse(_tempManifestPath);
if (!_remoteManifest->isLoaded())
{
CCLOG("AssetsManagerEx : Error parsing manifest file\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_PARSE_MANIFEST);
_updateState = State::UNCHECKED;
}
else
{
if (_localManifest->versionEquals(_remoteManifest))
{
_updateState = State::UP_TO_DATE;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);
}
else
{
_updateState = State::NEED_UPDATE;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);
if (_waitToUpdate)
{
startUpdate();
}
}
}
}
void AssetsManagerEx::startUpdate()
{
if (_updateState != State::NEED_UPDATE)
return;
_updateState = State::UPDATING;
_FailedUnits.clear();
_downloadUnits.clear();
_compressedFiles.clear();
_totalWaitToDownload = _totalToDownload = 0;
_percent = _percentByFile = _sizeCollected = _totalSize = 0;
_downloadedSize.clear();
_totalEnabled = false;
if (_tempManifest->isLoaded() && _tempManifest->versionEquals(_remoteManifest))
{
_tempManifest->genResumeAssetsList(&_downloadUnits);
_totalWaitToDownload = _totalToDownload = (int)_downloadUnits.size();
_downloader->batchDownloadAsync(_downloadUnits,BATCH_UPDATE_ID);
std::string msg = StringUtils::format("Resuming from prevIoUs unfinished update,%d files remains to be finished.",_totalToDownload);
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION,msg);
}
else
{
_tempManifest->release();
_tempManifest = _remoteManifest;
std::unordered_map<std::string,Manifest::AssetDiff> diff_map = _localManifest->genDiff(_remoteManifest);
if (diff_map.size() == 0)
{
updateSucceed();
}
else
{
std::string packageUrl = _remoteManifest->getPackageUrl();
for (auto it = diff_map.begin(); it != diff_map.end(); ++it)
{
Manifest::AssetDiff diff = it->second;
if (diff.type == Manifest::DiffType::DELETED)
{
_fileUtils->removeFile(_storagePath + diff.asset.path);
}
else
{
std::string path = diff.asset.path;
_fileUtils->createDirectory(basename(_storagePath + path));
Downloader::DownloadUnit unit;
unit.customId = it->first;
unit.srcUrl = packageUrl + path;
unit.storagePath = _storagePath + path;
unit.resumeDownload = false;
_downloadUnits.emplace(unit.customId,unit);
}
}
auto assets = _remoteManifest->getAssets();
for (auto it = assets.cbegin(); it != assets.cend(); ++it)
{
const std::string &key = it->first;
auto diffIt = diff_map.find(key);
if (diffIt == diff_map.end())
{
_tempManifest->setAssetDownloadState(key,Manifest::DownloadState::SUCCESSED);
}
}
_totalWaitToDownload = _totalToDownload = (int)_downloadUnits.size();
_downloader->batchDownloadAsync(_downloadUnits,BATCH_UPDATE_ID);
std::string msg = StringUtils::format("Start to update %d files from remote package.",_totalToDownload);
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION,msg);
}
}
_waitToUpdate = false;
}
void AssetsManagerEx::updateSucceed()
{
_fileUtils->renameFile(_storagePath,TEMP_MANIFEST_FILENAME,MANIFEST_FILENAME);
if (_localManifest != nullptr)
_localManifest->release();
_localManifest = _remoteManifest;
_remoteManifest = nullptr;
prepareLocalManifest();
decompressDownloadedZip();
_updateState = State::UP_TO_DATE;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_FINISHED);
}
void AssetsManagerEx::checkUpdate()
{
if (!_inited){
CCLOG("AssetsManagerEx : Manifests uninited.\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
return;
}
if (!_localManifest->isLoaded())
{
CCLOG("AssetsManagerEx : No local manifest file found error.\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
return;
}
switch (_updateState) {
case State::UNCHECKED:
case State::PREDOWNLOAD_VERSION:
{
downloadVersion();
}
break;
case State::UP_TO_DATE:
{
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);
}
break;
case State::FAIL_TO_UPDATE:
case State::NEED_UPDATE:
{
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);
}
break;
default:
break;
}
}
void AssetsManagerEx::update()
{
if (!_inited){
CCLOG("AssetsManagerEx : Manifests uninited.\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
return;
}
if (!_localManifest->isLoaded())
{
CCLOG("AssetsManagerEx : No local manifest file found error.\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
return;
}
_waitToUpdate = true;
switch (_updateState) {
case State::UNCHECKED:
{
_updateState = State::PREDOWNLOAD_VERSION;
}
case State::PREDOWNLOAD_VERSION:
{
downloadVersion();
}
break;
case State::VERSION_LOADED:
{
parseVersion();
}
break;
case State::PREDOWNLOAD_MANIFEST:
{
downloadManifest();
}
break;
case State::MANIFEST_LOADED:
{
parseManifest();
}
break;
case State::FAIL_TO_UPDATE:
case State::NEED_UPDATE:
{
if (!_remoteManifest->isLoaded())
{
_waitToUpdate = true;
_updateState = State::PREDOWNLOAD_MANIFEST;
downloadManifest();
}
else
{
startUpdate();
}
}
break;
case State::UP_TO_DATE:
case State::UPDATING:
_waitToUpdate = false;
break;
default:
break;
}
}
void AssetsManagerEx::updateAssets(const Downloader::DownloadUnits& assets)
{
if (!_inited){
CCLOG("AssetsManagerEx : Manifests uninited.\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
return;
}
if (_updateState != State::UPDATING && _localManifest->isLoaded() && _remoteManifest->isLoaded())
{
int size = (int)(assets.size());
if (size > 0)
{
_updateState = State::UPDATING;
_downloadUnits.clear();
_downloadUnits = assets;
_downloader->batchDownloadAsync(_downloadUnits,BATCH_UPDATE_ID);
}
else if (size == 0 && _totalWaitToDownload == 0)
{
updateSucceed();
}
}
}
const Downloader::DownloadUnits& AssetsManagerEx::getFailedAssets() const
{
return _FailedUnits;
}
void AssetsManagerEx::downloadFailedAssets()
{
CCLOG("AssetsManagerEx : Start update %lu Failed assets.\n",_FailedUnits.size());
updateAssets(_FailedUnits);
}
void AssetsManagerEx::onError(const Downloader::Error &error)
{
if (error.customId == VERSION_ID)
{
CCLOG("AssetsManagerEx : Fail to download version file,step skipped\n");
_updateState = State::PREDOWNLOAD_MANIFEST;
downloadManifest();
}
else if (error.customId == MANIFEST_ID)
{
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST,error.customId,error.message,error.curle_code,error.curlm_code);
}
else
{
auto unitIt = _downloadUnits.find(error.customId);
if (unitIt != _downloadUnits.end())
{
Downloader::DownloadUnit unit = unitIt->second;
_FailedUnits.emplace(unit.customId,unit);
}
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_UPDATING,error.curlm_code);
}
}
void AssetsManagerEx::onProgress(double total,double downloaded,const std::string &url,const std::string &customId)
{
if (customId == VERSION_ID || customId == MANIFEST_ID)
{
_percent = 100 * downloaded / total;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION,customId);
return;
}
else
{
bool found = false;
double totalDownloaded = 0;
for (auto it = _downloadedSize.begin(); it != _downloadedSize.end(); ++it)
{
if (it->first == customId)
{
it->second = downloaded;
found = true;
}
totalDownloaded += it->second;
}
if (!found)
{
_tempManifest->setAssetDownloadState(customId,Manifest::DownloadState::DOWNLOADING);
_downloadedSize.emplace(customId,downloaded);
_totalSize += total;
_sizeCollected++;
if (_sizeCollected == _totalToDownload)
{
_totalEnabled = true;
}
}
if (_totalEnabled && _updateState == State::UPDATING)
{
float currentPercent = 100 * totalDownloaded / _totalSize;
if ((int)currentPercent != (int)_percent) {
_percent = currentPercent;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION,customId);
}
}
}
}
void AssetsManagerEx::onSuccess(const std::string &srcUrl,const std::string &storagePath,const std::string &customId)
{
if (customId == VERSION_ID)
{
_updateState = State::VERSION_LOADED;
parseVersion();
}
else if (customId == MANIFEST_ID)
{
_updateState = State::MANIFEST_LOADED;
parseManifest();
}
else if (customId == BATCH_UPDATE_ID)
{
if (_FailedUnits.size() > 0 || _totalWaitToDownload > 0)
{
_tempManifest->saveToFile(_tempManifestPath);
decompressDownloadedZip();
_updateState = State::FAIL_TO_UPDATE;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_Failed);
}
else
{
updateSucceed();
}
}
else
{
auto assets = _remoteManifest->getAssets();
auto assetIt = assets.find(customId);
if (assetIt != assets.end())
{
_tempManifest->setAssetDownloadState(customId,Manifest::DownloadState::SUCCESSED);
if (assetIt->second.compressed) {
_compressedFiles.push_back(storagePath);
}
}
auto unitIt = _downloadUnits.find(customId);
if (unitIt != _downloadUnits.end())
{
_totalWaitToDownload--;
_percentByFile = 100 * (float)(_totalToDownload - _totalWaitToDownload) / _totalToDownload;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION,"");
}
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ASSET_UPDATED,customId);
unitIt = _FailedUnits.find(customId);
if (unitIt != _FailedUnits.end())
{
_FailedUnits.erase(unitIt);
}
}
}
void AssetsManagerEx::destroyDownloadedVersion()
{
_fileUtils->removeFile(_cacheVersionPath);
_fileUtils->removeFile(_cacheManifestPath);
}
NS_CC_EXT_END