大家在使用Cococ Creator提供的热更新 assetsManagers ,做md5校验的时候,一定会遇到卡顿的问题。
备注:文末有完整实现源码
原因是 Cococ Creator 官方提供的热更新校验回调是在ui线程进行,如下代码所示:
assetsManagers.setVerifyCallback(function (path, asset)) {
let compressed = asset.compressed;
let expectedMD5 = asset.md5;
let relativePath = asset.path;
let size = asset.size;
if (compressed) {
cc.log(`Verification passed : ${relativePath}`);
....
return true;
}
else {
cc.log(`Verification passed : ${relativePath} ( ${expectedMD5} )`);
....
return true;
}
});
为了解决这个问题,需要对引擎热更新部分稍加改造。
下面以android 环境为例进行详细说明。
第一步:增加 md5库, 放在 cocos2d-x/extensions/assets-manager文件夹下 。
第二步,改造AssetsManagerEx.h 和 AssetsManagerEx.cpp 增加默认校验方法
在AssetsManagerEx.h 增加函数声明:
private:
virtual bool onVerifyDefault(const std::string storagePath, Manifest::Asset asset);
在AssetsManagerEx.cpp 实现函数:
bool AssetsManagerEx::onVerifyDefault(const std::string storagePath,Manifest::Asset asset)
{
//cocos2d::log("onVerifyDefault 0");
Data data = cocos2d::FileUtils::getInstance()->getDataFromFile(storagePath);
if (data.isNull() ||(data.getSize()== 0 )){
CCLOG("onVerifyDefault 1");
return false;
}
std::string result = md5(data.getBytes(), data.getSize());
//cocos2d::log("onVerifyDefault:%s - assetmd5:%s-resultmd5:%s",storagePath.c_str(),asset.md5.c_str(),result.c_str());
if (memcmp(result.c_str(), asset.md5.c_str(), result.length()) == 0){
CCLOG("onVerifyDefault 2");
return true;
}
// cocos2d::log("onVerifyDefault 3");
return false;
}
在 AssetsManagerEx.cpp 的 onSuccess方法中,做md5比较部分改造。
当_verifyCallback 为nullptr 时,采用默认 onVerifyDefault 进行校验,如下:
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 (_downloadingTask.find(customId) != _downloadingTask.end()) {
_downloadingTask.erase(customId);
}
bool ok = true;
auto &assets = _remoteManifest->getAssets();
auto assetIt = assets.find(customId);
if (assetIt != assets.end())
{
Manifest::Asset asset = assetIt->second;
if (_verifyCallback != nullptr)
{
ok = _verifyCallback(storagePath, asset);
} else{
ok =onVerifyDefault( storagePath,asset);
}
}
if (ok)
{
bool compressed = assetIt != assets.end() ? assetIt->second.compressed : false;
if (compressed)
{
decompressDownloadedZip(customId, storagePath);
}
else
{
fileSuccess(customId, storagePath);
}
}
else
{
fileError(customId, "Asset file verification failed after downloaded");
}
}
}
第三步,修改 Android.mk,增加MD5.cpp 文件的编译
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := cocos_extension_static
LOCAL_MODULE_FILENAME := libextension
ifeq ($(USE_ARM_MODE),1)
LOCAL_ARM_MODE := arm
endif
LOCAL_SRC_FILES := \
assets-manager/MD5.cpp \
assets-manager/Manifest.cpp \
assets-manager/AssetsManagerEx.cpp \
assets-manager/CCEventAssetsManagerEx.cpp \
assets-manager/CCAsyncTaskPool.cpp \
LOCAL_CXXFLAGS += -fexceptions
LOCAL_C_INCLUDES := $(LOCAL_PATH)/. \
$(LOCAL_PATH)/.. \
$(LOCAL_PATH)/../cocos \
$(LOCAL_PATH)/../cocos/platform \
$(LOCAL_PATH)/../external/sources
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/. \
$(LOCAL_PATH)/..
include $(BUILD_STATIC_LIBRARY)
第四步,删除上层校验回调实现,默认采用c++新增的校验方法。
即:js 或ts 代码中不再调用 assetsManagers.setVerifyCallback 方法。
以上就是 Cocos Creator 热更新资源 md5值比较的调整,此方案可有效解决渲染线程卡顿问题。
下面是完整源码
MD5.h源码
#ifndef _LUA_MD5_H__
#define _LUA_MD5_H__
#include <stdio.h>
#include <string>
using namespace std;
class MD5 {
public:
typedef unsigned int size_type; // must be 32bit
MD5();
MD5(const std::string& text);
void update(const unsigned char *buf, size_type length);
void update(const char *buf, size_type length);
MD5& finalize();
std::string hexdigest(bool bUpper = false) const;
friend std::ostream& operator<<(std::ostream&, MD5 md5);
private:
void init();
typedef unsigned char uint1; // 8bit
typedef unsigned int uint4; // 32bit
enum {
blocksize = 64
}; // VC6 won't eat a const static int here
void transform(const uint1 block[blocksize]);
static void decode(uint4 output[], const uint1 input[], size_type len);
static void encode(uint1 output[], const uint4 input[], size_type len);
bool finalized;
uint1 buffer[blocksize];
uint4 count[2];
uint4 state[4];
uint1 digest[16];
static inline uint4 F(uint4 x, uint4 y, uint4 z);
static inline uint4 G(uint4 x, uint4 y, uint4 z);
static inline uint4 H(uint4 x, uint4 y, uint4 z);
static inline uint4 I(uint4 x, uint4 y, uint4 z);
static inline uint4 rotate_left(uint4 x, int n);
static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,
uint4 ac);
static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,
uint4 ac);
static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,
uint4 ac);
static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,
uint4 ac);
};
extern std::string md5(const std::string str);
extern std::string md5(const wchar_t* pwstr);
extern std::string md5(const unsigned char *buf, const unsigned int length);
#endif
MD5.cpp文件源码
//MD5.cpp
/* MD5
based on:
md5.h and md5.c
reference implemantion of RFC 1321
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
/* interface header */
#include "MD5.h"
#include <stdlib.h>
/* system implementation headers */
// Constants for MD5Transform routine.
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21
///
// F, G, H and I are basic MD5 functions.
inline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) {
return x&y | ~x&z;
}
inline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) {
return x&z | y&~z;
}
inline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) {
return x^y^z;
}
inline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) {
return y ^ (x | ~z);
}
// rotate_left rotates x left n bits.
inline MD5::uint4 MD5::rotate_left(uint4 x, int n) {
return (x << n) | (x >> (32-n));
}
// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
// Rotation is separate from addition to prevent recomputation.
inline void MD5::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
a = rotate_left(a+ F(b,c,d) + x + ac, s) + b;
}
inline void MD5::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
a = rotate_left(a + G(b,c,d) + x + ac, s) + b;
}
inline void MD5::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
a = rotate_left(a + H(b,c,d) + x + ac, s) + b;
}
inline void MD5::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
a = rotate_left(a + I(b,c,d) + x + ac, s) + b;
}
//
// default ctor, just initailize
MD5::MD5()
{
init();
}
//
// nifty shortcut ctor, compute MD5 for string and finalize it right away
MD5::MD5(const std::string &text)
{
init();
update(text.c_str(), text.length());
finalize();
}
//
void MD5::init()
{
finalized=false;
count[0] = 0;
count[1] = 0;
// load magic initialization constants.
state[0] = 0x67452301;
state[1] = 0xefcdab89;
state[2] = 0x98badcfe;
state[3] = 0x10325476;
}
//
// decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4.
void MD5::decode(uint4 output[], const uint1 input[], size_type len)
{
for (unsigned int i = 0, j = 0; j < len; i++, j += 4)
output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) |
(((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24);
}
//
// encodes input (uint4) into output (unsigned char). Assumes len is
// a multiple of 4.
void MD5::encode(uint1 output[], const uint4 input[], size_type len)
{
for (size_type i = 0, j = 0; j < len; i++, j += 4) {
output[j] = input[i] & 0xff;
output[j+1] = (input[i] >> 8) & 0xff;
output[j+2] = (input[i] >> 16) & 0xff;
output[j+3] = (input[i] >> 24) & 0xff;
}
}
//
// apply MD5 algo on a block
void MD5::transform(const uint1 block[blocksize])
{
uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
decode (x, block, blocksize);
/* Round 1 */
FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
/* Round 2 */
GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
/* Round 3 */
HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
/* Round 4 */
II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
// Zeroize sensitive information.
memset(x, 0, sizeof x);
}
//
// MD5 block update operation. Continues an MD5 message-digest
// operation, processing another message block
void MD5::update(const unsigned char input[], size_type length)
{
// compute number of bytes mod 64
size_type index = count[0] / 8 % blocksize;
// Update number of bits
if ((count[0] += (length << 3)) < (length << 3))
count[1]++;
count[1] += (length >> 29);
// number of bytes we need to fill in buffer
size_type firstpart = 64 - index;
size_type i;
// transform as many times as possible.
if (length >= firstpart)
{
// fill buffer first, transform
memcpy(&buffer[index], input, firstpart);
transform(buffer);
// transform chunks of blocksize (64 bytes)
for (i = firstpart; i + blocksize <= length; i += blocksize)
transform(&input[i]);
index = 0;
}
else
i = 0;
// buffer remaining input
memcpy(&buffer[index], &input[i], length-i);
}
//
// for convenience provide a verson with signed char
void MD5::update(const char input[], size_type length)
{
update((const unsigned char*)input, length);
}
//
// MD5 finalization. Ends an MD5 message-digest operation, writing the
// the message digest and zeroizing the context.
MD5& MD5::finalize()
{
static unsigned char padding[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
if (!finalized) {
// Save number of bits
unsigned char bits[8];
encode(bits, count, 8);
// pad out to 56 mod 64.
size_type index = count[0] / 8 % 64;
size_type padLen = (index < 56) ? (56 - index) : (120 - index);
update(padding, padLen);
// Append length (before padding)
update(bits, 8);
// Store state in digest
encode(digest, state, 16);
// Zeroize sensitive information.
memset(buffer, 0, sizeof buffer);
memset(count, 0, sizeof count);
finalized=true;
}
return *this;
}
//
// return hex representation of digest as string
std::string MD5::hexdigest(bool bUpper) const
{
if (!finalized)
return "";
char buf[33];
for (int i=0; i<16; i++){
sprintf(buf+i*2, "%02x", digest[i]);
}
if (bUpper){
for(int i = 0;i<33;i++){
buf[i] = toupper((unsigned char)buf[i]);
}
}
buf[32]=0;
return std::string(buf);
}
//
std::ostream& operator<<(std::ostream& out, MD5 md5)
{
return out << md5.hexdigest();
}
//
std::string md5(const std::string str)
{
MD5 md5 = MD5(str);
return md5.hexdigest();
}
std::string md5(const wchar_t* pwstr)
{
if(pwstr == NULL)
return "";
char *ptmp = (char *)new char[2*wcslen(pwstr)+1];
memset(ptmp,0,2*wcslen(pwstr)+1);
wcstombs(ptmp,pwstr,2*wcslen(pwstr)+1);
MD5 md5 = MD5(ptmp);
delete [] ptmp;
return md5.hexdigest();
}
std::string md5(const unsigned char *buf, const unsigned int length)
{
MD5 md5 = MD5();
md5.update(buf, length);
md5.finalize();
return md5.hexdigest();
}
AssetsManagerEx.h 源码
/****************************************************************************
Copyright (c) 2013 cocos2d-x.org
Copyright (c) 2014-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#ifndef __AssetsManagerEx__
#define __AssetsManagerEx__
#include <string>
#include <unordered_map>
#include <vector>
#include "platform/CCFileUtils.h"
#include "network/CCDownloader.h"
#include "CCEventAssetsManagerEx.h"
#include "Manifest.h"
#include "extensions/ExtensionMacros.h"
#include "extensions/ExtensionExport.h"
#include "json/document-wrapper.h"
NS_CC_EXT_BEGIN
/**
* @brief This class is used to auto update resources, such as pictures or scripts.
*/
class CC_EX_DLL AssetsManagerEx : public Ref
{
public:
//! Update states
enum class State
{
UNINITED,
UNCHECKED,
PREDOWNLOAD_VERSION,
DOWNLOADING_VERSION,
VERSION_LOADED,
PREDOWNLOAD_MANIFEST,
DOWNLOADING_MANIFEST,
MANIFEST_LOADED,
NEED_UPDATE,
READY_TO_UPDATE,
UPDATING,
UNZIPPING,
UP_TO_DATE,
FAIL_TO_UPDATE
};
const static std::string VERSION_ID;
const static std::string MANIFEST_ID;
typedef std::function<int(const std::string& versionA, const std::string& versionB)> VersionCompareHandle;
typedef std::function<bool(const std::string& path, Manifest::Asset asset)> VerifyCallback;
typedef std::function<void(EventAssetsManagerEx *event)> EventCallback;
/** @brief Create function for creating a new AssetsManagerEx
@param manifestUrl The url for the local manifest file
@param storagePath The storage path for downloaded assets
@warning The cached manifest in your storage path have higher priority and will be searched first,
only if it doesn't exist, AssetsManagerEx will use the given manifestUrl.
*/
static AssetsManagerEx* create(const std::string &manifestUrl, const std::string &storagePath);
/** @brief Check out if there is a new version of manifest.
* You may use this method before updating, then let user determine whether
* he wants to update resources.
*/
void checkUpdate();
/** @brief Prepare the update process, this will cleanup download process flags, fill up download units with temporary manifest or remote manifest
*/
void prepareUpdate();
/** @brief Update with the current local manifest.
*/
void update();
/** @brief Reupdate all failed assets under the current AssetsManagerEx context
*/
void downloadFailedAssets();
/** @brief Gets the current update state.
*/
State getState() const;
/** @brief Gets storage path.
*/
const std::string& getStoragePath() const;
/** @brief Function for retrieving the local manifest object
*/
const Manifest* getLocalManifest() const;
/** @brief Load a custom local manifest object, the local manifest must be loaded already.
* You can only manually load local manifest when the update state is UNCHECKED, it will fail once the update process is began.
* This API will do the following things:
* 1. Reset storage path
* 2. Set local storage
* 3. Search for cached manifest and compare with the local manifest
* 4. Init temporary manifest and remote manifest
* If successfully load the given local manifest and inited other manifests, it will return true, otherwise it will return false
* @param localManifest The local manifest object to be set
* @param storagePath The local storage path
*/
bool loadLocalManifest(Manifest* localManifest, const std::string& storagePath);
/** @brief Load a local manifest from url.
* You can only manually load local manifest when the update state is UNCHECKED, it will fail once the update process is began.
* This API will do the following things:
* 1. Reset storage path
* 2. Set local storage
* 3. Search for cached manifest and compare with the local manifest
* 4. Init temporary manifest and remote manifest
* If successfully load the given local manifest and inited other manifests, it will return true, otherwise it will return false
* @param manifestUrl The local manifest url
*/
bool loadLocalManifest(const std::string& manifestUrl);
/** @brief Function for retrieving the remote manifest object
*/
const Manifest* getRemoteManifest() const;
/** @brief Load a custom remote manifest object, the manifest must be loaded already.
* You can only manually load remote manifest when the update state is UNCHECKED and local manifest is already inited, it will fail once the update process is began.
* @param remoteManifest The remote manifest object to be set
*/
bool loadRemoteManifest(Manifest* remoteManifest);
/** @brief Gets whether the current download is resuming previous unfinished job, this will only be available after READY_TO_UPDATE state, under unknown states it will return false by default.
*/
bool isResuming() const {return _downloadResumed;};
/** @brief Gets the total byte size to be downloaded of the update, this will only be available after READY_TO_UPDATE state, under unknown states it will return 0 by default.
*/
double getTotalBytes() const {return _totalSize;};
/** @brief Gets the current downloaded byte size of the update, this will only be available after READY_TO_UPDATE state, under unknown states it will return 0 by default.
*/
double getDownloadedBytes() const {return _totalDownloaded;};
/** @brief Gets the total files count to be downloaded of the update, this will only be available after READY_TO_UPDATE state, under unknown states it will return 0 by default.
*/
int getTotalFiles() const {return _totalToDownload;};
/** @brief Gets the current downloaded files count of the update, this will only be available after READY_TO_UPDATE state, under unknown states it will return 0 by default.
*/
int getDownloadedFiles() const {return _totalToDownload - _totalWaitToDownload;};
/** @brief Function for retrieving the max concurrent task count
*/
const int getMaxConcurrentTask() const {return _maxConcurrentTask;};
/** @brief Function for setting the max concurrent task count
*/
void setMaxConcurrentTask(const int max) {_maxConcurrentTask = max;};
/** @brief Set the handle function for comparing manifests versions
* @param handle The compare function
*/
void setVersionCompareHandle(const VersionCompareHandle& handle) {_versionCompareHandle = handle;};
/** @brief Set the verification function for checking whether downloaded asset is correct, e.g. using md5 verification
* @param callback The verify callback function
*/
void setVerifyCallback(const VerifyCallback& callback) {_verifyCallback = callback;};
/** @brief Set the event callback for receiving update process events
* @param callback The event callback function
*/
void setEventCallback(const EventCallback& callback) {_eventCallback = callback;};
/** @brief Cancel update
*/
void cancelUpdate();
/**
* @brief 设置热更新地址,由于热更新地址会动态的发生变化,热更新的地址以下发的地址为准,设置热更新地址后,会自动的替换所有热更新的源地址
*/
void setHotUpdateUrl(const std::string& url) { _hotUpdateUrl = url; };
CC_CONSTRUCTOR_ACCESS:
AssetsManagerEx(const std::string& manifestUrl, const std::string& storagePath);
AssetsManagerEx(const std::string& manifestUrl, const std::string& storagePath, const VersionCompareHandle& handle);
virtual ~AssetsManagerEx();
protected:
void init(const std::string& manifestUrl, const std::string& storagePath);
std::string basename(const std::string& path) const;
std::string get(const std::string& key) const;
void initManifests();
void prepareLocalManifest();
void setStoragePath(const std::string& storagePath);
void adjustPath(std::string &path);
void dispatchUpdateEvent(EventAssetsManagerEx::EventCode code, const std::string &message = "", const std::string &assetId = "", int curle_code = 0, int curlm_code = 0);
void downloadVersion();
void parseVersion();
void downloadManifest();
void parseManifest();
void startUpdate();
void updateSucceed();
bool decompress(const std::string &filename);
void decompressDownloadedZip(const std::string &customId, const std::string &storagePath);
/** @brief Update a list of assets under the current AssetsManagerEx context
*/
void updateAssets(const DownloadUnits& assets);
/** @brief Retrieve all failed assets during the last update
*/
const DownloadUnits& getFailedAssets() const;
/** @brief Function for destroying the downloaded version file and manifest file
*/
void destroyDownloadedVersion();
/** @brief Download items in queue with max concurrency setting
*/
void queueDowload();
void fileError(const std::string& identifier, const std::string& errorStr, int errorCode = 0, int errorCodeInternal = 0);
void fileSuccess(const std::string &customId, const std::string &storagePath);
/** @brief Call back function for error handling,
the error will then be reported to user's listener registed in addUpdateEventListener
@param error The error object contains ErrorCode, message, asset url, asset key
@warning AssetsManagerEx internal use only
* @js NA
* @lua NA
*/
virtual void onError(const network::DownloadTask& task,
int errorCode,
int errorCodeInternal,
const std::string& errorStr);
/** @brief Call back function for recording downloading percent of the current asset,
the progression will then be reported to user's listener registed in addUpdateProgressEventListener
@param total Total size to download for this asset
@param downloaded Total size already downloaded for this asset
@param url The url of this asset
@param customId The key of this asset
@warning AssetsManagerEx internal use only
* @js NA
* @lua NA
*/
virtual void onProgress(double total, double downloaded, const std::string &url, const std::string &customId);
/** @brief Call back function for success of the current asset
the success event will then be send to user's listener registed in addUpdateEventListener
@param srcUrl The url of this asset
@param customId The key of this asset
@warning AssetsManagerEx internal use only
* @js NA
* @lua NA
*/
virtual void onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId);
private:
virtual bool onVerifyDefault(const std::string storagePath, Manifest::Asset asset);
private:
void batchDownload();
// Called when one DownloadUnits finished
void onDownloadUnitsFinished();
//! The event of the current AssetsManagerEx in event dispatcher
std::string _eventName;
//! Reference to the global event dispatcher
// EventDispatcher *_eventDispatcher;
//! Reference to the global file utils
FileUtils *_fileUtils;
//! State of update
State _updateState;
//! Downloader
std::shared_ptr<network::Downloader> _downloader;
//! The reference to the local assets
const std::unordered_map<std::string, Manifest::Asset> *_assets;
//! The path to store successfully downloaded version.
std::string _storagePath;
//! The path to store downloading version.
std::string _tempStoragePath;
//! The local path of cached temporary version file
std::string _tempVersionPath;
//! The local path of cached manifest file
std::string _cacheManifestPath;
//! The local path of cached temporary manifest file
std::string _tempManifestPath;
//! The path of local manifest file
std::string _manifestUrl;
//! Local manifest
Manifest *_localManifest;
//! Local temporary manifest for download resuming
Manifest *_tempManifest;
//! Remote manifest
Manifest *_remoteManifest;
//! Whether user have requested to update
enum class UpdateEntry : char
{
NONE,
CHECK_UPDATE,
DO_UPDATE
};
UpdateEntry _updateEntry;
//! All assets unit to download
DownloadUnits _downloadUnits;
//! All failed units
DownloadUnits _failedUnits;
//! Download queue
std::vector<std::string> _queue;
bool _downloadResumed;
//! Max concurrent task count for downloading
int _maxConcurrentTask;
//! Current concurrent task count
int _currConcurrentTask;
//! Download percent
float _percent;
//! Download percent by file
float _percentByFile;
//! Indicate whether the total size should be enabled
int _totalEnabled;
//! Indicate the number of file whose total size have been collected
int _sizeCollected;
//! Total file size need to be downloaded (sum of all files)
double _totalSize;
//! Total downloaded file size (sum of all downloaded files)
double _totalDownloaded;
//! Downloaded size for each file
std::unordered_map<std::string, double> _downloadedSize;
//! Total number of assets to download
int _totalToDownload;
//! Total number of assets still waiting to be downloaded
int _totalWaitToDownload;
//! Next target percent for saving the manifest file
float _nextSavePoint;
//! Handle function to compare versions between different manifests
VersionCompareHandle _versionCompareHandle;
//! Callback function to verify the downloaded assets
VerifyCallback _verifyCallback;
//! Callback function to dispatch events
EventCallback _eventCallback;
//! Marker for whether the assets manager is inited
bool _inited;
//! Marker for whether the update is canceled
bool _canceled;
//! Downloading task container
std::unordered_map<std::string, std::shared_ptr<const network::DownloadTask>> _downloadingTask;
/*是否启用资源下载类型*/
bool _isUsingAssetsType;
/*资源类型 ""为大厅 其它为子游戏包名*/
std::string _assetsType;
/* 热更新地址*/
std::string _hotUpdateUrl;
};
NS_CC_EXT_END
#endif /* defined(__AssetsManagerEx__) */
AssetsManagerEx.cpp 源码
/****************************************************************************
Copyright (c) 2014 cocos2d-x.org
Copyright (c) 2015-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "AssetsManagerEx.h"
#include "base/ccUTF8.h"
#include "CCAsyncTaskPool.h"
#include <stdio.h>
#include <errno.h>
#include "MD5.h"
#ifdef MINIZIP_FROM_SYSTEM
#include <minizip/unzip.h>
#else // from our embedded sources
#include "unzip/unzip.h"
#endif
NS_CC_EXT_BEGIN
#define VERSION_FILENAME "version.manifest"
#define TEMP_MANIFEST_FILENAME "project.manifest.temp"
#define TEMP_PACKAGE_SUFFIX "_temp"
#define MANIFEST_FILENAME "project.manifest"
#define BUFFER_SIZE 8192
#define MAX_FILENAME 512
#define DEFAULT_CONNECTION_TIMEOUT 45
#define SAVE_POINT_INTERVAL 0.1
const std::string AssetsManagerEx::VERSION_ID = "@version";
const std::string AssetsManagerEx::MANIFEST_ID = "@manifest";
// Implementation of AssetsManagerEx
AssetsManagerEx::AssetsManagerEx(const std::string& manifestUrl, const std::string& storagePath)
: _updateState(State::UNINITED)
, _assets(nullptr)
, _storagePath("")
, _tempVersionPath("")
, _cacheManifestPath("")
, _tempManifestPath("")
, _localManifest(nullptr)
, _tempManifest(nullptr)
, _remoteManifest(nullptr)
, _updateEntry(UpdateEntry::NONE)
, _percent(0)
, _percentByFile(0)
, _totalSize(0)
, _sizeCollected(0)
, _totalDownloaded(0)
, _totalToDownload(0)
, _totalWaitToDownload(0)
, _nextSavePoint(0.0)
, _downloadResumed(false)
, _maxConcurrentTask(32)
, _currConcurrentTask(0)
, _verifyCallback(nullptr)
, _inited(false)
, _canceled(false)
{
init(manifestUrl, storagePath);
}
AssetsManagerEx::AssetsManagerEx(const std::string& manifestUrl, const std::string& storagePath, const VersionCompareHandle& handle)
: _updateState(State::UNINITED)
, _assets(nullptr)
, _storagePath("")
, _tempVersionPath("")
, _cacheManifestPath("")
, _tempManifestPath("")
, _localManifest(nullptr)
, _tempManifest(nullptr)
, _remoteManifest(nullptr)
, _updateEntry(UpdateEntry::NONE)
, _percent(0)
, _percentByFile(0)
, _totalSize(0)
, _sizeCollected(0)
, _totalDownloaded(0)
, _totalToDownload(0)
, _totalWaitToDownload(0)
, _nextSavePoint(0.0)
, _downloadResumed(false)
, _maxConcurrentTask(32)
, _currConcurrentTask(0)
, _versionCompareHandle(handle)
, _verifyCallback(nullptr)
, _eventCallback(nullptr)
, _inited(false)
, _isUsingAssetsType(false)
, _assetsType("")
, _hotUpdateUrl("")
{
init(manifestUrl, storagePath);
}
void AssetsManagerEx::init(const std::string& manifestUrl, const std::string& storagePath)
{
// Init variables
std::string pointer = StringUtils::format("%p", this);
_eventName = "__cc_assets_manager_" + pointer;
_fileUtils = FileUtils::getInstance();
network::DownloaderHints hints =
{
static_cast<uint32_t>(_maxConcurrentTask),
DEFAULT_CONNECTION_TIMEOUT,
".tmp"
};
_downloader = std::shared_ptr<network::Downloader>(new network::Downloader(hints));
_downloader->onTaskError = std::bind(&AssetsManagerEx::onError, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
_downloader->onTaskProgress = [this](const network::DownloadTask& task,
int64_t /*bytesReceived*/,
int64_t totalBytesReceived,
int64_t totalBytesExpected)
{
this->onProgress(totalBytesExpected, totalBytesReceived, task.requestURL, task.identifier);
};
_downloader->onFileTaskSuccess = [this](const network::DownloadTask& task)
{
this->onSuccess(task.requestURL, task.storagePath, task.identifier);
};
setStoragePath(storagePath);
//这里做一下处理,当传入的是一个特定的格式时,修改当前3个缓存的路径
std::string typeString = "type.";
auto pos = manifestUrl.find(typeString);
if ( pos != std::string::npos) {
auto amType = manifestUrl.substr(typeString.size());
this->_isUsingAssetsType = true;
if (amType == "hall") {
//大厅
this->_assetsType = "";
_tempVersionPath = _tempStoragePath + VERSION_FILENAME;
_cacheManifestPath = _storagePath + MANIFEST_FILENAME;
_tempManifestPath = _tempStoragePath + TEMP_MANIFEST_FILENAME;
}
else {
this->_assetsType = amType;
_tempVersionPath = _tempStoragePath + amType + VERSION_FILENAME;
_cacheManifestPath = _storagePath + amType + MANIFEST_FILENAME;
_tempManifestPath = _tempStoragePath + amType + TEMP_MANIFEST_FILENAME;
}
}
else {
this->_isUsingAssetsType = false;
this->_assetsType = "";
_tempVersionPath = _tempStoragePath + VERSION_FILENAME;
_cacheManifestPath = _storagePath + MANIFEST_FILENAME;
_tempManifestPath = _tempStoragePath + TEMP_MANIFEST_FILENAME;
if (manifestUrl.size() > 0)
{
loadLocalManifest(manifestUrl);
}
}
}
AssetsManagerEx::~AssetsManagerEx()
{
_downloader->onTaskError = (nullptr);
_downloader->onFileTaskSuccess = (nullptr);
_downloader->onTaskProgress = (nullptr);
CC_SAFE_RELEASE(_localManifest);
// _tempManifest could share a ptr with _remoteManifest or _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()
{
_inited = true;
_canceled = false;
// Init and load temporary manifest
_tempManifest = new (std::nothrow) Manifest();
if (_tempManifest)
{
_tempManifest->setHotUpdateUrl(_hotUpdateUrl);
_tempManifest->parseFile(_tempManifestPath);
// Previous update is interrupted
if (_fileUtils->isFileExist(_tempManifestPath))
{
// Manifest parse failed, remove all temp files
if (!_tempManifest->isLoaded())
{
_fileUtils->removeDirectory(_tempStoragePath);
CC_SAFE_RELEASE(_tempManifest);
_tempManifest = nullptr;
}
}
}
else
{
_inited = false;
}
// Init remote manifest for future usage
_remoteManifest = new (std::nothrow) Manifest();
if (!_remoteManifest)
{
_inited = false;
}
if (!_inited)
{
CC_SAFE_RELEASE(_localManifest);
CC_SAFE_RELEASE(_tempManifest);
CC_SAFE_RELEASE(_remoteManifest);
_localManifest = nullptr;
_tempManifest = nullptr;
_remoteManifest = nullptr;
}
}
void AssetsManagerEx::prepareLocalManifest()
{
// An alias to assets
_assets = &(_localManifest->getAssets());
// Add search paths
_localManifest->prependSearchPaths();
}
bool AssetsManagerEx::loadLocalManifest(Manifest* localManifest, const std::string& storagePath)
{
if (_updateState > State::UNINITED)
{
return false;
}
if (!localManifest || !localManifest->isLoaded())
{
return false;
}
_inited = true;
_canceled = false;
// Reset storage path
if (storagePath.size() > 0)
{
setStoragePath(storagePath);
_tempVersionPath = _tempStoragePath + VERSION_FILENAME;
_cacheManifestPath = _storagePath + MANIFEST_FILENAME;
_tempManifestPath = _tempStoragePath + TEMP_MANIFEST_FILENAME;
}
// Release existing local manifest
if (_localManifest)
{
CC_SAFE_RELEASE(_localManifest);
}
_localManifest = localManifest;
_localManifest->retain();
// Find the cached manifest file
Manifest *cachedManifest = nullptr;
if (_fileUtils->isFileExist(_cacheManifestPath))
{
cachedManifest = new (std::nothrow) Manifest();
if (cachedManifest)
{
cachedManifest->setHotUpdateUrl(_hotUpdateUrl);
cachedManifest->parseFile(_cacheManifestPath);
if (!cachedManifest->isLoaded())
{
_fileUtils->removeFile(_cacheManifestPath);
CC_SAFE_RELEASE(cachedManifest);
cachedManifest = nullptr;
}
}
}
// Compare with cached manifest to determine which one to use
if (cachedManifest)
{
bool localNewer = _localManifest->versionGreater(cachedManifest, _versionCompareHandle);
if (localNewer)
{
// Recreate storage, to empty the content
_fileUtils->removeDirectory(_storagePath);
_fileUtils->createDirectory(_storagePath);
CC_SAFE_RELEASE(cachedManifest);
}
else
{
CC_SAFE_RELEASE(_localManifest);
_localManifest = cachedManifest;
}
}
prepareLocalManifest();
// Init temp manifest and remote manifest
initManifests();
if (!_inited)
{
return false;
}
else
{
_updateState = State::UNCHECKED;
return true;
}
}
bool AssetsManagerEx::loadLocalManifest(const std::string& manifestUrl)
{
if (manifestUrl.size() == 0)
{
return false;
}
if (_updateState > State::UNINITED)
{
return false;
}
_manifestUrl = manifestUrl;
// Init and load local manifest
_localManifest = new (std::nothrow) Manifest();
if (!_localManifest)
{
return false;
}
Manifest *cachedManifest = nullptr;
// Find the cached manifest file
if (_fileUtils->isFileExist(_cacheManifestPath))
{
cachedManifest = new (std::nothrow) Manifest();
if (cachedManifest)
{
cachedManifest->setHotUpdateUrl(_hotUpdateUrl);
cachedManifest->parseFile(_cacheManifestPath);
if (!cachedManifest->isLoaded())
{
_fileUtils->removeFile(_cacheManifestPath);
CC_SAFE_RELEASE(cachedManifest);
cachedManifest = nullptr;
}
}
}
// Ensure no search path of cached manifest is used to load this manifest
std::vector<std::string> searchPaths = _fileUtils->getSearchPaths();
if (cachedManifest)
{
std::vector<std::string> cacheSearchPaths = cachedManifest->getSearchPaths();
std::vector<std::string> trimmedPaths = searchPaths;
for (auto path : cacheSearchPaths)
{
const auto pos = std::find(trimmedPaths.begin(), trimmedPaths.end(), path);
if (pos != trimmedPaths.end())
{
trimmedPaths.erase(pos);
}
}
_fileUtils->setSearchPaths(trimmedPaths);
}
_localManifest->setHotUpdateUrl(_hotUpdateUrl);
// Load local manifest in app package
_localManifest->parseFile(_manifestUrl);
if (cachedManifest)
{
// Restore search paths
_fileUtils->setSearchPaths(searchPaths);
}
if (_localManifest->isLoaded())
{
// Compare with cached manifest to determine which one to use
if (cachedManifest)
{
bool localNewer = _localManifest->versionGreater(cachedManifest, _versionCompareHandle);
if (localNewer)
{
// Recreate storage, to empty the content
_fileUtils->removeDirectory(_storagePath);
_fileUtils->createDirectory(_storagePath);
CC_SAFE_RELEASE(cachedManifest);
}
else
{
CC_SAFE_RELEASE(_localManifest);
_localManifest = cachedManifest;
}
}
prepareLocalManifest();
}
// Fail to load local manifest
if (!_localManifest->isLoaded())
{
CCLOG("AssetsManagerEx : No local manifest file found error.\n");
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
return false;
}
initManifests();
_updateState = State::UNCHECKED;
return true;
}
bool AssetsManagerEx::loadRemoteManifest(Manifest* remoteManifest)
{
if (!_inited || _updateState > State::UNCHECKED)
{
return false;
}
if (!remoteManifest || !remoteManifest->isLoaded())
{
return false;
}
// Release existing remote manifest
if (_remoteManifest)
{
CC_SAFE_RELEASE(_remoteManifest);
}
_remoteManifest = remoteManifest;
_remoteManifest->retain();
// Compare manifest version and set state
if (_localManifest->versionGreaterOrEquals(_remoteManifest, _versionCompareHandle))
{
_updateState = State::UP_TO_DATE;
_fileUtils->removeDirectory(_tempStoragePath);
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);
}
else
{
_updateState = State::NEED_UPDATE;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);
}
return true;
}
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);
_tempStoragePath = _storagePath;
_tempStoragePath.insert(_storagePath.size() - 1, TEMP_PACKAGE_SUFFIX);
_fileUtils->createDirectory(_tempStoragePath);
}
void AssetsManagerEx::adjustPath(std::string &path)
{
if (path.size() > 0 && path[path.size() - 1] != '/')
{
path.append("/");
}
}
bool AssetsManagerEx::decompress(const std::string &zip)
{
// Find root path for zip file
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);
// Open the zip file
unzFile zipfile = unzOpen(FileUtils::getInstance()->getSuitableFOpen(zip).c_str());
if (! zipfile)
{
CCLOG("AssetsManagerEx : can not open downloaded zip file %s\n", zip.c_str());
return false;
}
// Get info about the zip file
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;
}
// Buffer to hold data read from the zip file
char readBuffer[BUFFER_SIZE];
// Loop to extract all files.
uLong i;
for (i = 0; i < global_info.number_entry; ++i)
{
// Get info about current file.
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;
// Check if this entry is a directory or a file.
const size_t filenameLength = strlen(fileName);
if (fileName[filenameLength-1] == '/')
{
//There are not directory entry in some case.
//So we need to create directory when decompressing file entry
if ( !_fileUtils->createDirectory(basename(fullPath)) )
{
// Failed to create directory
CCLOG("AssetsManagerEx : can not create directory %s\n", fullPath.c_str());
unzClose(zipfile);
return false;
}
}
else
{
// Create all directories in advance to avoid issue
std::string dir = basename(fullPath);
if (!_fileUtils->isDirectoryExist(dir)) {
if (!_fileUtils->createDirectory(dir)) {
// Failed to create directory
CCLOG("AssetsManagerEx : can not create directory %s\n", fullPath.c_str());
unzClose(zipfile);
return false;
}
}
// Entry is a file, so extract it.
// Open current file.
if (unzOpenCurrentFile(zipfile) != UNZ_OK)
{
CCLOG("AssetsManagerEx : can not extract file %s\n", fileName);
unzClose(zipfile);
return false;
}
// Create a file to store current file.
FILE *out = fopen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), "wb");
if (!out)
{
CCLOG("AssetsManagerEx : can not create decompress destination file %s (errno: %d)\n", fullPath.c_str(), errno);
unzCloseCurrentFile(zipfile);
unzClose(zipfile);
return false;
}
// Write current file content to destinate file.
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);
// Goto next entry listed in the zip file.
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(const std::string &customId, const std::string &storagePath)
{
struct AsyncData
{
std::string customId;
std::string zipFile;
bool succeed;
};
AsyncData* asyncData = new AsyncData;
asyncData->customId = customId;
asyncData->zipFile = storagePath;
asyncData->succeed = false;
std::function<void(void*)> decompressFinished = [this](void* param) {
auto dataInner = reinterpret_cast<AsyncData*>(param);
if (dataInner->succeed)
{
fileSuccess(dataInner->customId, dataInner->zipFile);
}
else
{
std::string errorMsg = "Unable to decompress file " + dataInner->zipFile;
// Ensure zip file deletion (if decompress failure cause task thread exit anormally)
_fileUtils->removeFile(dataInner->zipFile);
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DECOMPRESS, "", errorMsg);
fileError(dataInner->customId, errorMsg);
}
delete dataInner;
};
AsyncTaskPool::getInstance()->enqueue(AsyncTaskPool::TaskType::TASK_OTHER, decompressFinished, (void*)asyncData, [this, asyncData]() {
// Decompress all compressed files
if (decompress(asyncData->zipFile))
{
asyncData->succeed = true;
}
_fileUtils->removeFile(asyncData->zipFile);
});
}
void AssetsManagerEx::dispatchUpdateEvent(EventAssetsManagerEx::EventCode code, const std::string &assetId/* = ""*/, const std::string &message/* = ""*/, int curle_code/* = CURLE_OK*/, int curlm_code/* = CURLM_OK*/)
{
switch (code)
{
case EventAssetsManagerEx::EventCode::ERROR_UPDATING:
case EventAssetsManagerEx::EventCode::ERROR_PARSE_MANIFEST:
case EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST:
case EventAssetsManagerEx::EventCode::ERROR_DECOMPRESS:
case EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST:
case EventAssetsManagerEx::EventCode::UPDATE_FAILED:
case EventAssetsManagerEx::EventCode::UPDATE_FINISHED:
case EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE:
_updateEntry = UpdateEntry::NONE;
break;
case EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION:
break;
case EventAssetsManagerEx::EventCode::ASSET_UPDATED:
break;
case EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND:
if (_updateEntry == UpdateEntry::CHECK_UPDATE)
{
_updateEntry = UpdateEntry::NONE;
}
break;
default:
break;
}
if (_eventCallback != nullptr) {
EventAssetsManagerEx* event = new (std::nothrow) EventAssetsManagerEx(_eventName, this, code, assetId, message, curle_code, curlm_code);
_eventCallback(event);
event->release();
}
}
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;
// Download version file asynchronously
_downloader->createDownloadFileTask(versionUrl, _tempVersionPath, VERSION_ID);
}
// No version file found
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->setHotUpdateUrl(_hotUpdateUrl);
_remoteManifest->parseVersion(_tempVersionPath);
if (!_remoteManifest->isVersionLoaded())
{
CCLOG("AssetsManagerEx : Fail to parse version file, step skipped\n");
_updateState = State::PREDOWNLOAD_MANIFEST;
downloadManifest();
}
else
{
if (_localManifest->versionGreaterOrEquals(_remoteManifest, _versionCompareHandle))
{
_updateState = State::UP_TO_DATE;
_fileUtils->removeDirectory(_tempStoragePath);
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);
}
else
{
_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;
// Download version file asynchronously
_downloader->createDownloadFileTask(manifestUrl, _tempManifestPath, MANIFEST_ID);
}
// No manifest file found
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->setHotUpdateUrl(_hotUpdateUrl);
_remoteManifest->parseFile(_tempManifestPath);
if (!_remoteManifest->isLoaded())
{
CCLOG("AssetsManagerEx : Error parsing manifest file, %s", _tempManifestPath.c_str());
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_PARSE_MANIFEST);
_updateState = State::UNCHECKED;
}
else
{
if (_localManifest->versionGreaterOrEquals(_remoteManifest, _versionCompareHandle))
{
_updateState = State::UP_TO_DATE;
_fileUtils->removeDirectory(_tempStoragePath);
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);
}
else
{
_updateState = State::NEED_UPDATE;
if (_updateEntry == UpdateEntry::DO_UPDATE)
{
startUpdate();
}
else if (_updateEntry == UpdateEntry::CHECK_UPDATE)
{
prepareUpdate();
}
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);
}
}
}
void AssetsManagerEx::prepareUpdate()
{
if (_updateState != State::NEED_UPDATE)
return;
// Clean up before update
_failedUnits.clear();
_downloadUnits.clear();
_totalWaitToDownload = _totalToDownload = 0;
_nextSavePoint = 0;
_percent = _percentByFile = _sizeCollected = _totalDownloaded = _totalSize = 0;
_downloadResumed = false;
_downloadedSize.clear();
_totalEnabled = false;
// Temporary manifest exists, previously updating and equals to the remote version, resuming previous download
if (_tempManifest && _tempManifest->isLoaded() && _tempManifest->isUpdating() && _tempManifest->versionEquals(_remoteManifest))
{
_tempManifest->saveToFile(_tempManifestPath);
_tempManifest->genResumeAssetsList(&_downloadUnits);
_totalWaitToDownload = _totalToDownload = (int)_downloadUnits.size();
_downloadResumed = true;
// Collect total size
for(auto iter : _downloadUnits)
{
const DownloadUnit& unit = iter.second;
if (unit.size > 0)
{
_totalSize += unit.size;
}
}
}
else
{
// Temporary manifest exists, but can't be parsed or version doesn't equals remote manifest (out of date)
if (_tempManifest)
{
// Remove all temp files
_fileUtils->removeDirectory(_tempStoragePath);
CC_SAFE_RELEASE(_tempManifest);
// Recreate temp storage path and save remote manifest
_fileUtils->createDirectory(_tempStoragePath);
_remoteManifest->saveToFile(_tempManifestPath);
}
// Temporary manifest will be used to register the download states of each asset,
// in this case, it equals remote manifest.
_tempManifest = _remoteManifest;
// Check difference between local manifest and remote manifest
std::unordered_map<std::string, Manifest::AssetDiff> diff_map = _localManifest->genDiff(_remoteManifest);
if (diff_map.size() == 0)
{
updateSucceed();
return;
}
else
{
// Generate download units for all assets that need to be updated or added
std::string packageUrl = _remoteManifest->getPackageUrl();
// Preprocessing local files in previous version and creating download folders
for (auto it = diff_map.begin(); it != diff_map.end(); ++it)
{
Manifest::AssetDiff diff = it->second;
if (diff.type != Manifest::DiffType::DELETED)
{
std::string path = diff.asset.path;
DownloadUnit unit;
unit.customId = it->first;
unit.srcUrl = packageUrl + path + "?md5=" + diff.asset.md5;
unit.storagePath = _tempStoragePath + path;
unit.size = diff.asset.size;
_downloadUnits.emplace(unit.customId, unit);
_tempManifest->setAssetDownloadState(it->first, Manifest::DownloadState::UNSTARTED);
_totalSize += unit.size;
}
}
// Start updating the temp manifest
_tempManifest->setUpdating(true);
// Save current download manifest information for resuming
_tempManifest->saveToFile(_tempManifestPath);
_totalWaitToDownload = _totalToDownload = (int)_downloadUnits.size();
}
}
_updateState = State::READY_TO_UPDATE;
}
void AssetsManagerEx::startUpdate()
{
if (_updateState == State::NEED_UPDATE)
{
prepareUpdate();
}
if (_updateState == State::READY_TO_UPDATE)
{
_totalSize = 0;
_updateState = State::UPDATING;
std::string msg;
if (_downloadResumed)
{
msg = StringUtils::format("Resuming from previous unfinished update, %d files remains to be finished.", _totalToDownload);
}
else
{
msg = StringUtils::format("Start to update %d files from remote package.", _totalToDownload);
}
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, "", msg);
batchDownload();
}
}
void AssetsManagerEx::updateSucceed()
{
// Set temp manifest's updating
if (_tempManifest != nullptr) {
_tempManifest->setUpdating(false);
}
// Every thing is correctly downloaded, do the following
// 1. rename temporary manifest to valid manifest
if (this->_isUsingAssetsType) {
if (_fileUtils->isFileExist(_tempManifestPath)) {
_fileUtils->renameFile(_tempStoragePath, this->_assetsType + TEMP_MANIFEST_FILENAME, this->_assetsType + MANIFEST_FILENAME);
}
}else {
if (_fileUtils->isFileExist(_tempManifestPath)) {
_fileUtils->renameFile(_tempStoragePath, TEMP_MANIFEST_FILENAME, MANIFEST_FILENAME);
}
}
// 2. Get the delete files
std::unordered_map<std::string, Manifest::AssetDiff> diff_map = _localManifest->genDiff(_remoteManifest);
// 3. merge temporary storage path to storage path so that temporary version turns to cached version
if (_fileUtils->isDirectoryExist(_tempStoragePath))
{
// Merging all files in temp storage path to storage path
std::vector<std::string> files;
_fileUtils->listFilesRecursively(_tempStoragePath, &files);
int baseOffset = (int)_tempStoragePath.length();
std::string relativePath, dstPath;
for (std::vector<std::string>::iterator it = files.begin(); it != files.end(); ++it)
{
relativePath.assign((*it).substr(baseOffset));
dstPath.assign(_storagePath + relativePath);
// Create directory
if (relativePath.back() == '/')
{
_fileUtils->createDirectory(dstPath);
}
// Copy file
else
{
if (_fileUtils->isFileExist(dstPath))
{
_fileUtils->removeFile(dstPath);
}
_fileUtils->renameFile(*it, dstPath);
}
// Remove from delete list for safe, although this is not the case in general.
auto diff_itr = diff_map.find(relativePath);
if (diff_itr != diff_map.end()) {
diff_map.erase(diff_itr);
}
}
// Preprocessing local files in previous version and creating download folders
for (auto it = diff_map.begin(); it != diff_map.end(); ++it)
{
Manifest::AssetDiff diff = it->second;
if (diff.type == Manifest::DiffType::DELETED)
{
// TODO: Do this when download finish, it don’t matter delete or not.
std::string exsitedPath = _storagePath + diff.asset.path;
_fileUtils->removeFile(exsitedPath);
}
}
}
// 4. swap the localManifest
CC_SAFE_RELEASE(_localManifest);
_localManifest = _remoteManifest;
_localManifest->setManifestRoot(_storagePath);
_remoteManifest = nullptr;
// 5. make local manifest take effect
prepareLocalManifest();
// 6. Set update state
_updateState = State::UP_TO_DATE;
// 7. Notify finished event
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_FINISHED);
// 8. Remove temp storage path
_fileUtils->removeDirectory(_tempStoragePath);
}
void AssetsManagerEx::checkUpdate()
{
if (_updateEntry != UpdateEntry::NONE)
{
CCLOGERROR("AssetsManagerEx::checkUpdate, updateEntry isn't NONE");
return;
}
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;
}
_updateEntry = UpdateEntry::CHECK_UPDATE;
switch (_updateState) {
case State::FAIL_TO_UPDATE:
_updateState = State::UNCHECKED;
case State::UNCHECKED:
case State::PREDOWNLOAD_VERSION:
{
downloadVersion();
}
break;
case State::UP_TO_DATE:
{
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);
}
break;
case State::NEED_UPDATE:
{
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);
}
break;
default:
break;
}
}
void AssetsManagerEx::update()
{
if (_updateEntry != UpdateEntry::NONE)
{
CCLOGERROR("AssetsManagerEx::update, updateEntry isn't NONE");
return;
}
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;
}
_updateEntry = UpdateEntry::DO_UPDATE;
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::READY_TO_UPDATE:
case State::NEED_UPDATE:
{
// Manifest not loaded yet
if (!_remoteManifest->isLoaded())
{
_updateState = State::PREDOWNLOAD_MANIFEST;
downloadManifest();
}
else if (_updateEntry == UpdateEntry::DO_UPDATE)
{
startUpdate();
}
}
break;
case State::UP_TO_DATE:
case State::UPDATING:
case State::UNZIPPING:
_updateEntry = UpdateEntry::NONE;
break;
default:
break;
}
}
void AssetsManagerEx::updateAssets(const 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())
{
_updateState = State::UPDATING;
_downloadUnits.clear();
_downloadedSize.clear();
_percent = _percentByFile = _sizeCollected = _totalDownloaded = _totalSize = 0;
_totalWaitToDownload = _totalToDownload = (int)assets.size();
_nextSavePoint = 0;
_totalEnabled = false;
if (_totalToDownload > 0)
{
_downloadUnits = assets;
this->batchDownload();
}
else if (_totalToDownload == 0)
{
onDownloadUnitsFinished();
}
}
}
const DownloadUnits& AssetsManagerEx::getFailedAssets() const
{
return _failedUnits;
}
void AssetsManagerEx::downloadFailedAssets()
{
CCLOG("AssetsManagerEx : Start update %lu failed assets.\n", static_cast<unsigned long>(_failedUnits.size()));
updateAssets(_failedUnits);
}
void AssetsManagerEx::fileError(const std::string& identifier, const std::string& errorStr, int errorCode, int errorCodeInternal)
{
auto unitIt = _downloadUnits.find(identifier);
// Found unit and add it to failed units
if (unitIt != _downloadUnits.end())
{
_totalWaitToDownload--;
DownloadUnit unit = unitIt->second;
_failedUnits.emplace(unit.customId, unit);
}
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_UPDATING, identifier, errorStr, errorCode, errorCodeInternal);
_tempManifest->setAssetDownloadState(identifier, Manifest::DownloadState::UNSTARTED);
_currConcurrentTask = std::max(0, _currConcurrentTask-1);
queueDowload();
}
void AssetsManagerEx::fileSuccess(const std::string &customId, const std::string &storagePath)
{
// Set download state to SUCCESSED
_tempManifest->setAssetDownloadState(customId, Manifest::DownloadState::SUCCESSED);
auto unitIt = _failedUnits.find(customId);
// Found unit and delete it
if (unitIt != _failedUnits.end())
{
// Remove from failed units list
_failedUnits.erase(unitIt);
}
unitIt = _downloadUnits.find(customId);
if (unitIt != _downloadUnits.end())
{
// Reduce count only when unit found in _downloadUnits
_totalWaitToDownload--;
_percentByFile = 100 * (float)(_totalToDownload - _totalWaitToDownload) / _totalToDownload;
// Notify progression event
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, "");
}
// Notify asset updated event
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ASSET_UPDATED, customId);
_currConcurrentTask = std::max(0, _currConcurrentTask-1);
queueDowload();
}
void AssetsManagerEx::onError(const network::DownloadTask& task,
int errorCode,
int errorCodeInternal,
const std::string& errorStr)
{
// Skip version error occurred
if (task.identifier == VERSION_ID)
{
CCLOG("AssetsManagerEx : Fail to download version file, step skipped\n");
_updateState = State::PREDOWNLOAD_MANIFEST;
downloadManifest();
}
else if (task.identifier == MANIFEST_ID)
{
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST, task.identifier, errorStr, errorCode, errorCodeInternal);
_updateState = State::FAIL_TO_UPDATE;
}
else
{
if (_downloadingTask.find(task.identifier) != _downloadingTask.end()) {
_downloadingTask.erase(task.identifier);
}
fileError(task.identifier, errorStr, errorCode, errorCodeInternal);
}
}
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;
// Notify progression event
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, customId);
return;
}
else
{
// Calcul total downloaded
bool found = false;
_totalDownloaded = 0;
for (auto it = _downloadedSize.begin(); it != _downloadedSize.end(); ++it)
{
if (it->first == customId)
{
it->second = downloaded;
found = true;
}
_totalDownloaded += it->second;
}
// Collect information if not registed
if (!found)
{
// Set download state to DOWNLOADING, this will run only once in the download process
_tempManifest->setAssetDownloadState(customId, Manifest::DownloadState::DOWNLOADING);
// Register the download size information
_downloadedSize.emplace(customId, downloaded);
// Check download unit size existance, if not exist collect size in total size
if (_downloadUnits[customId].size == 0)
{
_totalSize += total;
_sizeCollected++;
// All collected, enable total size
if (_sizeCollected == _totalToDownload)
{
_totalEnabled = true;
}
}
}
if (_totalEnabled && _updateState == State::UPDATING)
{
float currentPercent = 100 * _totalDownloaded / _totalSize;
// Notify at integer level change
if ((int)currentPercent != (int)_percent) {
_percent = currentPercent;
// Notify progression event
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 (_downloadingTask.find(customId) != _downloadingTask.end()) {
_downloadingTask.erase(customId);
}
bool ok = true;
auto &assets = _remoteManifest->getAssets();
auto assetIt = assets.find(customId);
if (assetIt != assets.end())
{
Manifest::Asset asset = assetIt->second;
if (_verifyCallback != nullptr)
{
ok = _verifyCallback(storagePath, asset);
} else{
ok =onVerifyDefault( storagePath,asset);
}
}
if (ok)
{
bool compressed = assetIt != assets.end() ? assetIt->second.compressed : false;
if (compressed)
{
decompressDownloadedZip(customId, storagePath);
}
else
{
fileSuccess(customId, storagePath);
}
}
else
{
fileError(customId, "Asset file verification failed after downloaded");
}
}
}
bool AssetsManagerEx::onVerifyDefault(const std::string storagePath,Manifest::Asset asset)
{
//cocos2d::log("onVerifyDefault 0");
Data data = cocos2d::FileUtils::getInstance()->getDataFromFile(storagePath);
if (data.isNull() ||(data.getSize()== 0 )){
CCLOG("onVerifyDefault 1");
return false;
}
std::string result = md5(data.getBytes(), data.getSize());
//cocos2d::log("onVerifyDefault:%s - assetmd5:%s-resultmd5:%s",storagePath.c_str(),asset.md5.c_str(),result.c_str());
if (memcmp(result.c_str(), asset.md5.c_str(), result.length()) == 0){
CCLOG("onVerifyDefault 2");
return true;
}
// cocos2d::log("onVerifyDefault 3");
return false;
}
void AssetsManagerEx::destroyDownloadedVersion()
{
_fileUtils->removeDirectory(_storagePath);
_fileUtils->removeDirectory(_tempStoragePath);
}
void AssetsManagerEx::batchDownload()
{
_queue.clear();
for(auto iter : _downloadUnits)
{
const DownloadUnit& unit = iter.second;
if (unit.size > 0)
{
_totalSize += unit.size;
_sizeCollected++;
}
_queue.push_back(iter.first);
}
// All collected, enable total size
if (_sizeCollected == _totalToDownload)
{
_totalEnabled = true;
}
queueDowload();
}
void AssetsManagerEx::queueDowload()
{
if (_totalWaitToDownload == 0 || (_canceled && _currConcurrentTask == 0))
{
this->onDownloadUnitsFinished();
return;
}
while (_currConcurrentTask < _maxConcurrentTask && _queue.size() > 0 && !_canceled)
{
std::string key = _queue.back();
_queue.pop_back();
_currConcurrentTask++;
DownloadUnit& unit = _downloadUnits[key];
_fileUtils->createDirectory(basename(unit.storagePath));
auto downloadTask = _downloader->createDownloadFileTask(unit.srcUrl, unit.storagePath, unit.customId);
_downloadingTask.emplace(unit.customId, downloadTask);
_tempManifest->setAssetDownloadState(key, Manifest::DownloadState::DOWNLOADING);
}
if (_percentByFile / 100 > _nextSavePoint)
{
// Save current download manifest information for resuming
_tempManifest->saveToFile(_tempManifestPath);
_nextSavePoint += SAVE_POINT_INTERVAL;
}
}
void AssetsManagerEx::onDownloadUnitsFinished()
{
// Always save current download manifest information for resuming
_tempManifest->saveToFile(_tempManifestPath);
// Finished with error check
if (_failedUnits.size() > 0)
{
_updateState = State::FAIL_TO_UPDATE;
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_FAILED);
}
else if (_updateState == State::UPDATING)
{
updateSucceed();
}
}
void AssetsManagerEx::cancelUpdate()
{
if (_canceled)
{
return;
}
_canceled = true;
std::vector<std::shared_ptr<const network::DownloadTask>> tasks;
for (const auto& it : _downloadingTask)
{
tasks.push_back(it.second);
}
for (const auto& it : tasks)
{
_downloader->abort(*it);
}
_downloadingTask.clear();
}
NS_CC_EXT_END
|