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,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4);
_downloader->_onSuccess = std::bind(&AssetsManagerEx::onSuccess, this, std::placeholders::_1, std::placeholders::_2, 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,
NULL,
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", fileName, 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, this, 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.customId, error.message, error.curle_code, 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