├── res ├── HelloWorld.png ├── GetFilesMD5.exe ├── UpdateTotal.ver ├── Scenes │ ├── NoticeMsg.csb │ └── UpdateScene.csb ├── Default │ ├── Button_Disable.png │ └── LoadingBarFile.png └── UpdateFileList.ver ├── frameworks └── readme.txt ├── src ├── cocos │ └── readme.txt ├── packages │ └── mvc │ │ ├── init.lua │ │ ├── AppBase.lua │ │ └── ViewBase.lua ├── app │ ├── MyApp.lua │ ├── views │ │ └── MainScene.lua │ ├── md5.lua │ └── UpdateSystem.lua ├── main.lua └── config.lua ├── .cocos-project.json ├── .buildpath ├── .project ├── config.json └── README.md /res/HelloWorld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/res/HelloWorld.png -------------------------------------------------------------------------------- /frameworks/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/frameworks/readme.txt -------------------------------------------------------------------------------- /res/GetFilesMD5.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/res/GetFilesMD5.exe -------------------------------------------------------------------------------- /res/UpdateTotal.ver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/res/UpdateTotal.ver -------------------------------------------------------------------------------- /src/cocos/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/src/cocos/readme.txt -------------------------------------------------------------------------------- /res/Scenes/NoticeMsg.csb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/res/Scenes/NoticeMsg.csb -------------------------------------------------------------------------------- /res/Scenes/UpdateScene.csb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/res/Scenes/UpdateScene.csb -------------------------------------------------------------------------------- /res/Default/Button_Disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/res/Default/Button_Disable.png -------------------------------------------------------------------------------- /res/Default/LoadingBarFile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailufeiba/luaFilesUpdate/HEAD/res/Default/LoadingBarFile.png -------------------------------------------------------------------------------- /.cocos-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "engine_version": "cocos2d-x-3.15", 3 | "has_native": true, 4 | "project_type": "lua" 5 | } -------------------------------------------------------------------------------- /src/packages/mvc/init.lua: -------------------------------------------------------------------------------- 1 | 2 | local _M = {} 3 | 4 | _M.AppBase = import(".AppBase") 5 | _M.ViewBase = import(".ViewBase") 6 | 7 | return _M 8 | -------------------------------------------------------------------------------- /src/app/MyApp.lua: -------------------------------------------------------------------------------- 1 | 2 | local MyApp = class("MyApp", cc.load("mvc").AppBase) 3 | 4 | function MyApp:onCreate() 5 | math.randomseed(os.time()) 6 | end 7 | 8 | return MyApp 9 | -------------------------------------------------------------------------------- /.buildpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main.lua: -------------------------------------------------------------------------------- 1 | cc.FileUtils:getInstance():setPopupNotify(false) 2 | 3 | require "config" 4 | require "cocos.init" 5 | 6 | local function main() 7 | require("app.MyApp"):create():run() 8 | end 9 | 10 | local status, msg = xpcall(main, __G__TRACKBACK__) 11 | if not status then 12 | print(msg) 13 | end 14 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | luaFilesUpdate 4 | 5 | 6 | 7 | 8 | 9 | 10 | org.ccdt.cocosproject 11 | org.eclipse.koneki.ldt.nature 12 | 13 | 14 | -------------------------------------------------------------------------------- /res/UpdateFileList.ver: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "Button_Disable.png": { 4 | "MD5": "FA79821349DD686F516187283BC2FB8C", 5 | "size": 1111 6 | }, 7 | "LoadingBarFile.png": { 8 | "MD5": "53A230F3F1B2D64E87A742DEBFD489BD", 9 | "size": 1115 10 | } 11 | }, 12 | "HelloWorld.png": { 13 | "MD5": "55EA4E952BF080F300379EC26723598B", 14 | "size": 37864 15 | }, 16 | "Scenes": { 17 | "NoticeMsg.csb": { 18 | "MD5": "3BD06BA08A0EE1F50B323BFB95203FD6", 19 | "size": 2728 20 | }, 21 | "UpdateScene.csb": { 22 | "MD5": "79E2E116DA20993F5F836C65F99D9211", 23 | "size": 2036 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/config.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 0 - disable debug info, 1 - less debug info, 2 - verbose debug info 3 | DEBUG = 2 4 | 5 | -- use framework, will disable all deprecated API, false - use legacy API 6 | CC_USE_FRAMEWORK = true 7 | 8 | -- show FPS on screen 9 | CC_SHOW_FPS = true 10 | 11 | -- disable create unexpected global variable 12 | CC_DISABLE_GLOBAL = true 13 | 14 | -- for module display 15 | CC_DESIGN_RESOLUTION = { 16 | width = 960, 17 | height = 640, 18 | autoscale = "FIXED_HEIGHT", 19 | callback = function(framesize) 20 | local ratio = framesize.width / framesize.height 21 | if ratio <= 1.34 then 22 | -- iPad 768*1024(1536*2048) is 4:3 screen 23 | return {autoscale = "FIXED_WIDTH"} 24 | end 25 | end 26 | } 27 | -------------------------------------------------------------------------------- /src/app/views/MainScene.lua: -------------------------------------------------------------------------------- 1 | cc.exports.UpdateSystem = require("app.UpdateSystem" ) 2 | 3 | local MainScene = class("MainScene", cc.load("mvc").ViewBase) 4 | 5 | function MainScene:onCreate() 6 | -- add background image 7 | display.newSprite("HelloWorld.png") 8 | :move(display.center) 9 | :addTo(self) 10 | 11 | -- add HelloWorld label 12 | cc.Label:createWithSystemFont("Hello World", "Arial", 40) 13 | :move(display.cx, display.cy + 200) 14 | :addTo(self) 15 | 16 | self:StartUpdate() 17 | end 18 | 19 | function MainScene:StartUpdate() 20 | local us = UpdateSystem:Create() 21 | us:SetUpdateProgressCallback( handler( self, self.UpdateProgress ) ) 22 | us:SetUpdateDoneCallback( handler( self, self.UpdateDone ) ) 23 | us:SetUpdateErrorCallback( handler( self, self.UpdateError ) ) 24 | us:StartUpdate( "UpdateTotal.ver", "UpdateFileList.ver" ) 25 | end 26 | 27 | function MainScene:UpdateProgress( currSize, totalSize ) 28 | print( "MainScene:UpdateProgress "..currSize.."/"..totalSize ) 29 | end 30 | 31 | function MainScene:UpdateDone() 32 | print( "MainScene:UpdateDone" ) 33 | end 34 | 35 | function MainScene:UpdateError( err ) 36 | dump( err, "MainScene:UpdateError err" ) 37 | end 38 | 39 | return MainScene 40 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "init_cfg":{ 3 | "isLandscape": true, 4 | "isWindowTop": false, 5 | "name": "luaFilesUpdate", 6 | "width": 960, 7 | "height": 640, 8 | "entry": "src/main.lua", 9 | "consolePort": 6050, 10 | "uploadPort": 6060 11 | }, 12 | "simulator_screen_size": [ 13 | { 14 | "title": "iPhone 3Gs (480x320)", 15 | "width": 480, 16 | "height": 320 17 | }, 18 | { 19 | "title": "iPhone 4 (960x640)", 20 | "width": 960, 21 | "height": 640 22 | }, 23 | { 24 | "title": "iPhone 5 (1136x640)", 25 | "width": 1136, 26 | "height": 640 27 | }, 28 | { 29 | "title": "iPad (1024x768)", 30 | "width": 1024, 31 | "height": 768 32 | }, 33 | { 34 | "title": "iPad Retina (2048x1536)", 35 | "width": 2048, 36 | "height": 1536 37 | }, 38 | { 39 | "title": "Android (800x480)", 40 | "width": 800, 41 | "height": 480 42 | }, 43 | { 44 | "title": "Android (854x480)", 45 | "width": 854, 46 | "height": 480 47 | }, 48 | { 49 | "title": "Android (1280x720)", 50 | "width": 1280, 51 | "height": 720 52 | }, 53 | { 54 | "title": "Android (1920x1080)", 55 | "width": 1920, 56 | "height": 1080 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /src/packages/mvc/AppBase.lua: -------------------------------------------------------------------------------- 1 | 2 | local AppBase = class("AppBase") 3 | 4 | function AppBase:ctor(configs) 5 | self.configs_ = { 6 | viewsRoot = "app.views", 7 | modelsRoot = "app.models", 8 | defaultSceneName = "MainScene", 9 | } 10 | 11 | for k, v in pairs(configs or {}) do 12 | self.configs_[k] = v 13 | end 14 | 15 | if type(self.configs_.viewsRoot) ~= "table" then 16 | self.configs_.viewsRoot = {self.configs_.viewsRoot} 17 | end 18 | if type(self.configs_.modelsRoot) ~= "table" then 19 | self.configs_.modelsRoot = {self.configs_.modelsRoot} 20 | end 21 | 22 | if DEBUG > 1 then 23 | dump(self.configs_, "AppBase configs") 24 | end 25 | 26 | if CC_SHOW_FPS then 27 | cc.Director:getInstance():setDisplayStats(true) 28 | end 29 | 30 | -- event 31 | self:onCreate() 32 | end 33 | 34 | function AppBase:run(initSceneName) 35 | initSceneName = initSceneName or self.configs_.defaultSceneName 36 | self:enterScene(initSceneName) 37 | end 38 | 39 | function AppBase:enterScene(sceneName, transition, time, more) 40 | local view = self:createView(sceneName) 41 | view:showWithScene(transition, time, more) 42 | return view 43 | end 44 | 45 | function AppBase:createView(name) 46 | for _, root in ipairs(self.configs_.viewsRoot) do 47 | local packageName = string.format("%s.%s", root, name) 48 | local status, view = xpcall(function() 49 | return require(packageName) 50 | end, function(msg) 51 | if not string.find(msg, string.format("'%s' not found:", packageName)) then 52 | print("load view error: ", msg) 53 | end 54 | end) 55 | local t = type(view) 56 | if status and (t == "table" or t == "userdata") then 57 | return view:create(self, name) 58 | end 59 | end 60 | error(string.format("AppBase:createView() - not found view \"%s\" in search paths \"%s\"", 61 | name, table.concat(self.configs_.viewsRoot, ",")), 0) 62 | end 63 | 64 | function AppBase:onCreate() 65 | end 66 | 67 | return AppBase 68 | -------------------------------------------------------------------------------- /src/packages/mvc/ViewBase.lua: -------------------------------------------------------------------------------- 1 | 2 | local ViewBase = class("ViewBase", cc.Node) 3 | 4 | function ViewBase:ctor(app, name) 5 | self:enableNodeEvents() 6 | self.app_ = app 7 | self.name_ = name 8 | 9 | -- check CSB resource file 10 | local res = rawget(self.class, "RESOURCE_FILENAME") 11 | if res then 12 | self:createResourceNode(res) 13 | end 14 | 15 | local binding = rawget(self.class, "RESOURCE_BINDING") 16 | if res and binding then 17 | self:createResourceBinding(binding) 18 | end 19 | 20 | if self.onCreate then self:onCreate() end 21 | end 22 | 23 | function ViewBase:getApp() 24 | return self.app_ 25 | end 26 | 27 | function ViewBase:getName() 28 | return self.name_ 29 | end 30 | 31 | function ViewBase:getResourceNode() 32 | return self.resourceNode_ 33 | end 34 | 35 | function ViewBase:createResourceNode(resourceFilename) 36 | if self.resourceNode_ then 37 | self.resourceNode_:removeSelf() 38 | self.resourceNode_ = nil 39 | end 40 | self.resourceNode_ = cc.CSLoader:createNode(resourceFilename) 41 | assert(self.resourceNode_, string.format("ViewBase:createResourceNode() - load resouce node from file \"%s\" failed", resourceFilename)) 42 | self:addChild(self.resourceNode_) 43 | end 44 | 45 | function ViewBase:createResourceBinding(binding) 46 | assert(self.resourceNode_, "ViewBase:createResourceBinding() - not load resource node") 47 | for nodeName, nodeBinding in pairs(binding) do 48 | local node = self.resourceNode_:getChildByName(nodeName) 49 | if nodeBinding.varname then 50 | self[nodeBinding.varname] = node 51 | end 52 | for _, event in ipairs(nodeBinding.events or {}) do 53 | if event.event == "touch" then 54 | node:onTouch(handler(self, self[event.method])) 55 | end 56 | end 57 | end 58 | end 59 | 60 | function ViewBase:showWithScene(transition, time, more) 61 | self:setVisible(true) 62 | local scene = display.newScene(self.name_) 63 | scene:addChild(self) 64 | display.runScene(scene, transition, time, more) 65 | return self 66 | end 67 | 68 | return ViewBase 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # luaFilesUpdate 2 | FilesUpdate 3 | github排版有问题,请raw阅读 4 | 5 | 项目简介 6 | 这是一个基于cocos2d-x 3.x lua版的文件更新 7 | 8 |  请把读写目录第一个加入到收索目录! 9 | FileUtils::getInstance()->addSearchPath(FileUtils::getInstance()->getWritablePath()); 10 | 11 | 12 | UpdateSystem.lua 简介 13 | 更新入口 14 | function UpdateSystem:StartUpdate( localFilePath, localFileListPath ) 15 | 参数 localFilePath:本地[更新总文件]的路径 (后面讲如何配置这个文件) 16 | 参数 localFileListPath:本地文件列表 (后面讲如何配置这个文件) 17 | 18 | 第一步 校验MD5 19 | 1 读取本地[更新总文件],获得更新的基本信息 20 | function UpdateSystem:LoadTotalUpdateFile() 21 | 22 | 2 请求服务器[更新总文件],获得服务器上的基本信息(服务器文件地址在本地[更新总文件]里配置的) 23 | function UpdateSystem:RequestServerTotalUpdateFile() 24 | 25 | 3 请求道服务器[更新总文件]后,对比客户端和服务器的[更新总文件] 26 | function UpdateSystem:OnServerTotalResponse() 27 | function UpdateSystem:CheckUpdate() 28 | 通过对比本地和服务器文件里的MD5值,可以知道当前需不需要更新,如果不需要更新,则更新完成 29 | 如果需要更新,并且服务器当前设置了强更,则更新失败,同时删除所有在文件列表并且在读写目录的文件 30 | 如果需要更新,并且服务器当前设置可更新,但是客户端当前版本低于服务器设置的上一个强更版本,则更新失败,同时删除所有在文件列表并且在读写目录的文件 31 | 更新流程进入第二步 32 | 33 | 第二步 对比文件列表,获得更新列表 34 | function UpdateSystem:StartCompareFileList() 35 | 1 读取本地文件列表 36 | function UpdateSystem:LoadClientFileList() 37 | 2 获取服务器的文件列表(服务器文件列表地址在服务器的[更新总文件]里配置的) 38 | function UpdateSystem:RequestServerFileList() 39 | 3 对比文件列表,获得更新列表 40 | function UpdateSystem:OnServerFileListResponse() 41 | function UpdateSystem:GetUpdateFileList() 42 | 更新流程进入第三步 43 | 44 | 第三步 根据更新列表进行文件下载 45 | 0 开始下载文件 46 | function UpdateSystem:DownloadNext() 47 | 此处会根据取消标记,结束更新流程,更新错误(更新被取消) 48 | 49 | 1 收到服务器文件后,对文件内容进行md5运算,得到文件内容的MD5值 50 | 拿这个值跟服务器文件列表里该文件的MD5值进行对比 51 | 如果不同,说明文件内容有问题,重新下载,重试三次.(如果三次都对不上,说明配置错误或者网络错误,更新失败) 52 | 53 | 2 如果文件校验通过,则储存这个文件 54 | function UpdateSystem:SaveDownloadFile( filename, data ) 55 | 创建该文件的目录 56 | 给该文件名加上临时后缀 ".updtmp" 57 | 如果存在该文件名+临时后缀的文件,则删除 58 | 将该文件内容储存到 文件名+".updtmp" 59 | 60 | 3 通知外部回调函数,当前的进度 61 | function UpdateSystem:UpdateProgresses() 62 | 63 | 4 从更新列表中删除该文件,开始下载下一个文件,进入步骤0 64 | 65 | 5 所有文件下载成功 66 | function UpdateSystem:DownloadAllDone() 67 | 68 | function UpdateSystem:ChangeAllTmpToWork() 69 | 把所有更新列表里的文件并且在读写目录带有.updtmp的文件,去掉.updtmp后缀 70 | 71 | function UpdateSystem:RecordFileList() 72 | 更新本地的文件列表 73 | 74 | function UpdateSystem:RecordTotal() 75 | 更新本地[更新总文件] 76 | 77 | 6 function UpdateSystem:UpdateDone() 78 | 更新完成,通知外部回调函数 79 | 80 | 81 | 如何使用 82 | 参见:function MainScene:StartUpdate() 83 | local us = UpdateSystem:Create() 84 | us:SetUpdateProgressCallback( handler( self, self.UpdateProgress ) ) --设置进度回调函数 85 | us:SetUpdateDoneCallback( handler( self, self.UpdateDone ) ) --设置更新完成回调函数 86 | us:SetUpdateErrorCallback( handler( self, self.UpdateError ) ) --设置更新错误回调函数,传入参数参见UpdateSystem.lua顶部 87 | us:StartUpdate( "UpdateTotal.ver", "UpdateFileList.ver" ) --传入本地[更新总文件]和本地文件列表地址,开始更新流程 88 | 89 | 取消函数 90 | function UpdateSystem:Cancel() 91 | 调用该函数后,会在下载下一个文件之前停止流程,并回调更新错误回调函数,参数是ERROR_UPDATE_CALCEN 92 | 93 | 94 | 95 | 配置文件 96 | 本地[更新总文件]的配置 json格式,参见UpdateTotal.ver 97 | md5:所有文件内容md5值相加字符串的md5值 98 | currversion:当前版本号 99 | url:服务器[更新总文件]的地址 100 | 101 | --其余参数无用,保留其他参数是为了方便配置服务器[更新总文件] 102 | 103 | 服务器[更新总文件]的配置 json格式,UpdateFileList.ver 104 | md5:所有文件内容md5值相加字符串的md5值 105 | compulsive:当前版本是否需要强制更新 106 | currversion:当前版本号 107 | lastversion:上一个强制更新的版本号 108 | url:服务器的文件列表地址 --注意:此处与客户端不同 109 | desc:当前版本的更新内容 110 | 111 | 本地文件列表的配置 json格式 参见UpdateFileList.ver 112 | 具体不多说,直接看例子 113 | 114 | 服务器文件列表的配置 json格式 参见UpdateFileList.ver 115 | 参见客户端文件列表的配置 116 | 注意:加入一个元素 117 | "url":"" 指向服务器文件的下载地址的开头,比如"test.update.com/" 118 | 119 | 配置工具 120 | GetFilesMD5.exe 121 | 运行该工具会自动生成UpdateFileList.ver,并且返回一个MD5值,你需要将这个值填写到[更新总文件]的MD5值那里 122 | 该工具会忽略.exe .ver文件 123 | 124 | 特别注意:服务器UpdateFileList.ver别忘了手动加入 125 | "url":"" 指向服务器文件的下载地址的开头,比如"test.update.com/" 126 | 127 | https://github.com/bailufeiba/GetFilesMD5 128 | 129 | 如果可以的话,请保留第一行,让我装个逼 (づ ̄ 3 ̄)づ 130 | 如有问题QQ联系:2686885181 131 | -------------------------------------------------------------------------------- /src/app/md5.lua: -------------------------------------------------------------------------------- 1 | local md5 = { 2 | _VERSION = "md5.lua 1.1.0", 3 | _DESCRIPTION = "MD5 computation in Lua (5.1-3, LuaJIT)", 4 | _URL = "https://github.com/kikito/md5.lua", 5 | _LICENSE = [[ 6 | MIT LICENSE 7 | 8 | Copyright (c) 2013 Enrique García Cota + Adam Baldwin + hanzao + Equi 4 Software 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a 11 | copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included 19 | in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | ]] 29 | } 30 | 31 | -- bit lib implementions 32 | 33 | local char, byte, format, rep, sub = 34 | string.char, string.byte, string.format, string.rep, string.sub 35 | local bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift 36 | 37 | local ok, bit = pcall(require, 'bit') 38 | if ok then 39 | bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift = bit.bor, bit.band, bit.bnot, bit.bxor, bit.rshift, bit.lshift 40 | else 41 | ok, bit = pcall(require, 'bit32') 42 | 43 | if ok then 44 | 45 | bit_not = bit.bnot 46 | 47 | local tobit = function(n) 48 | return n <= 0x7fffffff and n or -(bit_not(n) + 1) 49 | end 50 | 51 | local normalize = function(f) 52 | return function(a,b) return tobit(f(tobit(a), tobit(b))) end 53 | end 54 | 55 | bit_or, bit_and, bit_xor = normalize(bit.bor), normalize(bit.band), normalize(bit.bxor) 56 | bit_rshift, bit_lshift = normalize(bit.rshift), normalize(bit.lshift) 57 | 58 | else 59 | 60 | local function tbl2number(tbl) 61 | local result = 0 62 | local power = 1 63 | for i = 1, #tbl do 64 | result = result + tbl[i] * power 65 | power = power * 2 66 | end 67 | return result 68 | end 69 | 70 | local function expand(t1, t2) 71 | local big, small = t1, t2 72 | if(#big < #small) then 73 | big, small = small, big 74 | end 75 | -- expand small 76 | for i = #small + 1, #big do 77 | small[i] = 0 78 | end 79 | end 80 | 81 | local to_bits -- needs to be declared before bit_not 82 | 83 | bit_not = function(n) 84 | local tbl = to_bits(n) 85 | local size = math.max(#tbl, 32) 86 | for i = 1, size do 87 | if(tbl[i] == 1) then 88 | tbl[i] = 0 89 | else 90 | tbl[i] = 1 91 | end 92 | end 93 | return tbl2number(tbl) 94 | end 95 | 96 | -- defined as local above 97 | to_bits = function (n) 98 | if(n < 0) then 99 | -- negative 100 | return to_bits(bit_not(math.abs(n)) + 1) 101 | end 102 | -- to bits table 103 | local tbl = {} 104 | local cnt = 1 105 | local last 106 | while n > 0 do 107 | last = n % 2 108 | tbl[cnt] = last 109 | n = (n-last)/2 110 | cnt = cnt + 1 111 | end 112 | 113 | return tbl 114 | end 115 | 116 | bit_or = function(m, n) 117 | local tbl_m = to_bits(m) 118 | local tbl_n = to_bits(n) 119 | expand(tbl_m, tbl_n) 120 | 121 | local tbl = {} 122 | for i = 1, #tbl_m do 123 | if(tbl_m[i]== 0 and tbl_n[i] == 0) then 124 | tbl[i] = 0 125 | else 126 | tbl[i] = 1 127 | end 128 | end 129 | 130 | return tbl2number(tbl) 131 | end 132 | 133 | bit_and = function(m, n) 134 | local tbl_m = to_bits(m) 135 | local tbl_n = to_bits(n) 136 | expand(tbl_m, tbl_n) 137 | 138 | local tbl = {} 139 | for i = 1, #tbl_m do 140 | if(tbl_m[i]== 0 or tbl_n[i] == 0) then 141 | tbl[i] = 0 142 | else 143 | tbl[i] = 1 144 | end 145 | end 146 | 147 | return tbl2number(tbl) 148 | end 149 | 150 | bit_xor = function(m, n) 151 | local tbl_m = to_bits(m) 152 | local tbl_n = to_bits(n) 153 | expand(tbl_m, tbl_n) 154 | 155 | local tbl = {} 156 | for i = 1, #tbl_m do 157 | if(tbl_m[i] ~= tbl_n[i]) then 158 | tbl[i] = 1 159 | else 160 | tbl[i] = 0 161 | end 162 | end 163 | 164 | return tbl2number(tbl) 165 | end 166 | 167 | bit_rshift = function(n, bits) 168 | local high_bit = 0 169 | if(n < 0) then 170 | -- negative 171 | n = bit_not(math.abs(n)) + 1 172 | high_bit = 0x80000000 173 | end 174 | 175 | local floor = math.floor 176 | 177 | for i=1, bits do 178 | n = n/2 179 | n = bit_or(floor(n), high_bit) 180 | end 181 | return floor(n) 182 | end 183 | 184 | bit_lshift = function(n, bits) 185 | if(n < 0) then 186 | -- negative 187 | n = bit_not(math.abs(n)) + 1 188 | end 189 | 190 | for i=1, bits do 191 | n = n*2 192 | end 193 | return bit_and(n, 0xFFFFFFFF) 194 | end 195 | end 196 | end 197 | 198 | -- convert little-endian 32-bit int to a 4-char string 199 | local function lei2str(i) 200 | local f=function (s) return char( bit_and( bit_rshift(i, s), 255)) end 201 | return f(0)..f(8)..f(16)..f(24) 202 | end 203 | 204 | -- convert raw string to big-endian int 205 | local function str2bei(s) 206 | local v=0 207 | for i=1, #s do 208 | v = v * 256 + byte(s, i) 209 | end 210 | return v 211 | end 212 | 213 | -- convert raw string to little-endian int 214 | local function str2lei(s) 215 | local v=0 216 | for i = #s,1,-1 do 217 | v = v*256 + byte(s, i) 218 | end 219 | return v 220 | end 221 | 222 | -- cut up a string in little-endian ints of given size 223 | local function cut_le_str(s,...) 224 | local o, r = 1, {} 225 | local args = {...} 226 | for i=1, #args do 227 | table.insert(r, str2lei(sub(s, o, o + args[i] - 1))) 228 | o = o + args[i] 229 | end 230 | return r 231 | end 232 | 233 | local swap = function (w) return str2bei(lei2str(w)) end 234 | 235 | -- An MD5 mplementation in Lua, requires bitlib (hacked to use LuaBit from above, ugh) 236 | -- 10/02/2001 jcw@equi4.com 237 | 238 | local CONSTS = { 239 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 240 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 241 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 242 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 243 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 244 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 245 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 246 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 247 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 248 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 249 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 250 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 251 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 252 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 253 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 254 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, 255 | 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 256 | } 257 | 258 | local f=function (x,y,z) return bit_or(bit_and(x,y),bit_and(-x-1,z)) end 259 | local g=function (x,y,z) return bit_or(bit_and(x,z),bit_and(y,-z-1)) end 260 | local h=function (x,y,z) return bit_xor(x,bit_xor(y,z)) end 261 | local i=function (x,y,z) return bit_xor(y,bit_or(x,-z-1)) end 262 | local z=function (ff,a,b,c,d,x,s,ac) 263 | a=bit_and(a+ff(b,c,d)+x+ac,0xFFFFFFFF) 264 | -- be *very* careful that left shift does not cause rounding! 265 | return bit_or(bit_lshift(bit_and(a,bit_rshift(0xFFFFFFFF,s)),s),bit_rshift(a,32-s))+b 266 | end 267 | 268 | local function transform(A,B,C,D,X) 269 | local a,b,c,d=A,B,C,D 270 | local t=CONSTS 271 | 272 | a=z(f,a,b,c,d,X[ 0], 7,t[ 1]) 273 | d=z(f,d,a,b,c,X[ 1],12,t[ 2]) 274 | c=z(f,c,d,a,b,X[ 2],17,t[ 3]) 275 | b=z(f,b,c,d,a,X[ 3],22,t[ 4]) 276 | a=z(f,a,b,c,d,X[ 4], 7,t[ 5]) 277 | d=z(f,d,a,b,c,X[ 5],12,t[ 6]) 278 | c=z(f,c,d,a,b,X[ 6],17,t[ 7]) 279 | b=z(f,b,c,d,a,X[ 7],22,t[ 8]) 280 | a=z(f,a,b,c,d,X[ 8], 7,t[ 9]) 281 | d=z(f,d,a,b,c,X[ 9],12,t[10]) 282 | c=z(f,c,d,a,b,X[10],17,t[11]) 283 | b=z(f,b,c,d,a,X[11],22,t[12]) 284 | a=z(f,a,b,c,d,X[12], 7,t[13]) 285 | d=z(f,d,a,b,c,X[13],12,t[14]) 286 | c=z(f,c,d,a,b,X[14],17,t[15]) 287 | b=z(f,b,c,d,a,X[15],22,t[16]) 288 | 289 | a=z(g,a,b,c,d,X[ 1], 5,t[17]) 290 | d=z(g,d,a,b,c,X[ 6], 9,t[18]) 291 | c=z(g,c,d,a,b,X[11],14,t[19]) 292 | b=z(g,b,c,d,a,X[ 0],20,t[20]) 293 | a=z(g,a,b,c,d,X[ 5], 5,t[21]) 294 | d=z(g,d,a,b,c,X[10], 9,t[22]) 295 | c=z(g,c,d,a,b,X[15],14,t[23]) 296 | b=z(g,b,c,d,a,X[ 4],20,t[24]) 297 | a=z(g,a,b,c,d,X[ 9], 5,t[25]) 298 | d=z(g,d,a,b,c,X[14], 9,t[26]) 299 | c=z(g,c,d,a,b,X[ 3],14,t[27]) 300 | b=z(g,b,c,d,a,X[ 8],20,t[28]) 301 | a=z(g,a,b,c,d,X[13], 5,t[29]) 302 | d=z(g,d,a,b,c,X[ 2], 9,t[30]) 303 | c=z(g,c,d,a,b,X[ 7],14,t[31]) 304 | b=z(g,b,c,d,a,X[12],20,t[32]) 305 | 306 | a=z(h,a,b,c,d,X[ 5], 4,t[33]) 307 | d=z(h,d,a,b,c,X[ 8],11,t[34]) 308 | c=z(h,c,d,a,b,X[11],16,t[35]) 309 | b=z(h,b,c,d,a,X[14],23,t[36]) 310 | a=z(h,a,b,c,d,X[ 1], 4,t[37]) 311 | d=z(h,d,a,b,c,X[ 4],11,t[38]) 312 | c=z(h,c,d,a,b,X[ 7],16,t[39]) 313 | b=z(h,b,c,d,a,X[10],23,t[40]) 314 | a=z(h,a,b,c,d,X[13], 4,t[41]) 315 | d=z(h,d,a,b,c,X[ 0],11,t[42]) 316 | c=z(h,c,d,a,b,X[ 3],16,t[43]) 317 | b=z(h,b,c,d,a,X[ 6],23,t[44]) 318 | a=z(h,a,b,c,d,X[ 9], 4,t[45]) 319 | d=z(h,d,a,b,c,X[12],11,t[46]) 320 | c=z(h,c,d,a,b,X[15],16,t[47]) 321 | b=z(h,b,c,d,a,X[ 2],23,t[48]) 322 | 323 | a=z(i,a,b,c,d,X[ 0], 6,t[49]) 324 | d=z(i,d,a,b,c,X[ 7],10,t[50]) 325 | c=z(i,c,d,a,b,X[14],15,t[51]) 326 | b=z(i,b,c,d,a,X[ 5],21,t[52]) 327 | a=z(i,a,b,c,d,X[12], 6,t[53]) 328 | d=z(i,d,a,b,c,X[ 3],10,t[54]) 329 | c=z(i,c,d,a,b,X[10],15,t[55]) 330 | b=z(i,b,c,d,a,X[ 1],21,t[56]) 331 | a=z(i,a,b,c,d,X[ 8], 6,t[57]) 332 | d=z(i,d,a,b,c,X[15],10,t[58]) 333 | c=z(i,c,d,a,b,X[ 6],15,t[59]) 334 | b=z(i,b,c,d,a,X[13],21,t[60]) 335 | a=z(i,a,b,c,d,X[ 4], 6,t[61]) 336 | d=z(i,d,a,b,c,X[11],10,t[62]) 337 | c=z(i,c,d,a,b,X[ 2],15,t[63]) 338 | b=z(i,b,c,d,a,X[ 9],21,t[64]) 339 | 340 | return bit_and(A+a,0xFFFFFFFF),bit_and(B+b,0xFFFFFFFF), 341 | bit_and(C+c,0xFFFFFFFF),bit_and(D+d,0xFFFFFFFF) 342 | end 343 | 344 | ---------------------------------------------------------------- 345 | 346 | local function md5_update(self, s) 347 | self.pos = self.pos + #s 348 | s = self.buf .. s 349 | for ii = 1, #s - 63, 64 do 350 | local X = cut_le_str(sub(s,ii,ii+63),4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4) 351 | assert(#X == 16) 352 | X[0] = table.remove(X,1) -- zero based! 353 | self.a,self.b,self.c,self.d = transform(self.a,self.b,self.c,self.d,X) 354 | end 355 | self.buf = sub(s, math.floor(#s/64)*64 + 1, #s) 356 | return self 357 | end 358 | 359 | local function md5_finish(self) 360 | local msgLen = self.pos 361 | local padLen = 56 - msgLen % 64 362 | 363 | if msgLen % 64 > 56 then padLen = padLen + 64 end 364 | 365 | if padLen == 0 then padLen = 64 end 366 | 367 | local s = char(128) .. rep(char(0),padLen-1) .. lei2str(bit_and(8*msgLen, 0xFFFFFFFF)) .. lei2str(math.floor(msgLen/0x20000000)) 368 | md5_update(self, s) 369 | 370 | assert(self.pos % 64 == 0) 371 | return lei2str(self.a) .. lei2str(self.b) .. lei2str(self.c) .. lei2str(self.d) 372 | end 373 | 374 | ---------------------------------------------------------------- 375 | 376 | function md5.new() 377 | return { a = CONSTS[65], b = CONSTS[66], c = CONSTS[67], d = CONSTS[68], 378 | pos = 0, 379 | buf = '', 380 | update = md5_update, 381 | finish = md5_finish } 382 | end 383 | 384 | function md5.tohex(s) 385 | return format("%08x%08x%08x%08x", str2bei(sub(s, 1, 4)), str2bei(sub(s, 5, 8)), str2bei(sub(s, 9, 12)), str2bei(sub(s, 13, 16))) 386 | end 387 | 388 | function md5.sum(s) 389 | return md5.new():update(s):finish() 390 | end 391 | 392 | function md5.sumhexa(s) 393 | return md5.tohex(md5.sum(s)) 394 | end 395 | 396 | return md5 -------------------------------------------------------------------------------- /src/app/UpdateSystem.lua: -------------------------------------------------------------------------------- 1 | -----author:bailufeiba 2017/5/12 2:21:11----- 2 | require( "app.md5" ) 3 | local UpdateSystem = class( "UpdateSystem" ) 4 | 5 | local PRINT_LOG = true --日志开关 6 | 7 | local ERROR_STARTUPDATE_PARAM = { code=1, msg="参数错误" } 8 | local ERROR_LOCALTOTALFILE_CONTENT = { code=2, msg="本地[更新总文件]内容错误" } 9 | local ERROR_SERVERTOTALFILE_CONTENT = { code=3, msg="服务器[更新总文件]内容错误" } 10 | local ERROR_NETWORK = { code=4, msg="网络错误" } 11 | local ERROR_NEEDTO_UPDATECLIENT = { code=5, msg="强制更新客户端", detail="这里会放置新版更新的描述" } 12 | local ERROR_SERVER_FILELIST_CONTENT = { code=6, msg="服务器文件列表内容错误", detail="url" } 13 | local ERROR_UPDATE_CALCEN = { code=7, msg="更新被取消" } 14 | local ERROR_DOWNLOAD_FILE_CONTENT = { code=8, msg="文件内容校验错误", detail="" } 15 | local ERROR_WRITE_FILE = { code=9, msg="存储文件失败", detail="" } 16 | local ERROR_RECORD_FILE_TO_FILELIST = { code=10, msg="记录文件到文件列表失败", detail="" } 17 | local ERROR_FILE_RENAME = { code=11, msg="文件重命名失败", detail="" } 18 | local ERROR_FILE_NOT_EXIST = { code=12, msg="文件不存在", detail="" } 19 | local ERROR_RECORD_TOTAL = { code=13, msg="记录更新总文件失败" } 20 | 21 | local DEFAULT_VERSION_SIZE = 4 --版本段位数 比如:1.0.0.0是4段数 , 注意:版本号仅支持数字 22 | local DOWNLOAD_FILE_SUFFIX = ".updtmp" 23 | 24 | function UpdateSystem:Create() 25 | return self.new(self) 26 | end 27 | 28 | function UpdateSystem:Cancel() 29 | self.bUpdateContinue = false 30 | end 31 | 32 | function UpdateSystem:SetUpdateProgressCallback( func ) 33 | self.UpdateProgressCallback = func 34 | end 35 | 36 | function UpdateSystem:SetUpdateDoneCallback( func ) 37 | self.UpdateDoneCallback = func 38 | end 39 | 40 | function UpdateSystem:SetUpdateErrorCallback( func ) 41 | self.UpdateErrorCallback = func 42 | end 43 | 44 | --更新入口 localFilePath:本地更新文件(UpdateTotal.ver)的相对路径 45 | --localFileList:本地文件列表 46 | function UpdateSystem:StartUpdate( localFilePath, localFileListPath ) 47 | self:ResetAll() 48 | self.localTotalFilePath = localFilePath 49 | self.localFileListPath = localFileListPath 50 | 51 | self:LoadTotalUpdateFile( localFilePath ) 52 | self:RequestServerTotalUpdateFile() 53 | end 54 | 55 | function UpdateSystem:ResetAll() 56 | 57 | end 58 | 59 | ------------读取本地[更新总文件]------------ 60 | function UpdateSystem:LoadTotalUpdateFile( localFilePath ) 61 | self:PrintLog( "读取本地[更新总文件]" ) 62 | self:ResetCUpdateTotal() 63 | 64 | local data = "" 65 | local path = cc.FileUtils:getInstance():getWritablePath()..localFilePath --防呆 66 | if cc.FileUtils:getInstance():isFileExist( path ) then 67 | data = cc.FileUtils:getInstance():getStringFromFile( path ) 68 | else 69 | data = cc.FileUtils:getInstance():getStringFromFile( localFilePath ) 70 | end 71 | 72 | if type(data) ~= "string" or data == "" or data == nil then 73 | self:ErrorUpdate( ERROR_LOCALTOTALFILE_CONTENT ) 74 | return 75 | end 76 | 77 | local jsData = json.decode(data) 78 | if self:CheckLocalFileData(jsData) ~= true then 79 | self:ErrorUpdate( ERROR_LOCALTOTALFILE_CONTENT ) 80 | return 81 | end 82 | 83 | self.tbCUpdateTotal.md5 = jsData.md5 84 | self.tbCUpdateTotal.currversion = jsData.currversion 85 | self.tbCUpdateTotal.compulsive = jsData.compulsive or false 86 | self.tbCUpdateTotal.url = jsData.url 87 | end 88 | 89 | function UpdateSystem:ResetCUpdateTotal() 90 | self.tbCUpdateTotal = {} 91 | self.tbCUpdateTotal.md5 = "" 92 | self.tbCUpdateTotal.currversion = "1.0.0" 93 | self.tbCUpdateTotal.compulsive = false 94 | self.tbCUpdateTotal.url = "" 95 | end 96 | 97 | function UpdateSystem:CheckLocalFileData( tb ) 98 | if type(tb) ~= "table" then return false end 99 | if type(tb.md5) ~= "string" or tb.md5 == "" then return false end 100 | if type(tb.url) ~= "string" or tb.url == "" then return false end 101 | if type(tb.currversion) ~= "string" or tb.currversion == "" then return false end 102 | return true 103 | end 104 | 105 | 106 | 107 | ------------请求服务器[更新总文件]------------ 108 | function UpdateSystem:RequestServerTotalUpdateFile() 109 | self:PrintLog( "正在请求服务器更新总文件" ) 110 | self:ResetSUpdateTotal() 111 | self:HttpRequest( self.tbCUpdateTotal.url, handler(self, self.OnServerTotalResponse) ) 112 | end 113 | 114 | function UpdateSystem:ResetSUpdateTotal() 115 | self.tbSUpdateTotal = {} 116 | self.tbSUpdateTotal.md5 = "" 117 | self.tbSUpdateTotal.compulsive = false 118 | self.tbSUpdateTotal.url = "" 119 | self.tbSUpdateTotal.desc = "" 120 | end 121 | 122 | function UpdateSystem:OnServerTotalResponse() 123 | self:PrintLog( "收到[更新总文件]的响应 状态:"..self.xhr.readyState ) 124 | self.xhr:unregisterScriptHandler() 125 | 126 | if self.xhr.readyState == 4 and (self.xhr.status >= 200 and self.xhr.status < 207) then 127 | self:ResetSUpdateTotal() 128 | 129 | local jsData = json.decode( self.xhr.response ) 130 | if jsData == nil then return end 131 | if self:CheckServerTotalData(jsData) ~= true then 132 | self:ErrorUpdate(ERROR_SERVERTOTALFILE_CONTENT) 133 | return 134 | end 135 | 136 | self.tbSUpdateTotal = clone( jsData ) 137 | self.tbSUpdateTotal.md5 = jsData.md5 138 | self.tbSUpdateTotal.currversion = jsData.currversion 139 | self.tbSUpdateTotal.lastversion = jsData.lastversion 140 | self.tbSUpdateTotal.compulsive = jsData.compulsive 141 | self.tbSUpdateTotal.url = jsData.url 142 | self.tbSUpdateTotal.desc = jsData.desc or "" 143 | 144 | self:CheckUpdate() 145 | elseif self.xhr.readyState == 1 and self.xhr.status == 0 then 146 | self:ErrorUpdate( ERROR_NETWORK ) 147 | end 148 | end 149 | 150 | function UpdateSystem:CheckServerTotalData( tb ) 151 | if type(tb) ~= "table" then return false end 152 | if type(tb.md5) ~= "string" or tb.md5 == "" then return false end 153 | if type(tb.url) ~= "string" or tb.url == "" then return false end 154 | if type(tb.currversion) ~= "string" or tb.currversion == "" then return false end 155 | if type(tb.lastversion) ~= "string" or tb.lastversion == "" then return false end 156 | if type(tb.compulsive) ~= "boolean" then return false end 157 | return true 158 | end 159 | 160 | function UpdateSystem:HttpRequest( url, func ) 161 | if self.xhr == nil then 162 | self.xhr = cc.XMLHttpRequest:new() 163 | self.xhr:retain() 164 | end 165 | self.xhr.timeout = 30 -- 设置超时时间 166 | self.xhr.responseType = cc.XMLHTTPREQUEST_RESPONSE_JSON 167 | self.xhr:open("GET", url ) 168 | self.xhr:registerScriptHandler( func ) 169 | self.xhr:send() 170 | end 171 | 172 | 173 | 174 | ------------对比本地更新总文件和服务器更新总文件------------ 175 | function UpdateSystem:CheckUpdate() 176 | self:PrintLog( "对比本地MD5和服务器MD5" ) 177 | 178 | --对比总更新的MD5 179 | if self.tbCUpdateTotal.md5 == self.tbSUpdateTotal.md5 then 180 | self:UpdateDone() 181 | return 182 | end 183 | 184 | --如果需要强更 185 | if self.tbSUpdateTotal.compulsive then 186 | self:PrintLog( "检测到需要强制更新" ) 187 | self:RemoveAllDownloadFile() 188 | ERROR_NEEDTO_UPDATECLIENT.detail = self.tbSUpdateTotal.desc 189 | self:ErrorUpdate( ERROR_NEEDTO_UPDATECLIENT ) 190 | return 191 | end 192 | 193 | --客户端版本低于上次强更版本 194 | local nv = self:CompareVersion( self.tbCUpdateTotal.currversion, self.tbSUpdateTotal.lastversion, DEFAULT_VERSION_SIZE ) 195 | if nv < 0 then 196 | self:PrintLog( "检测到需要强制更新" ) 197 | self:RemoveAllDownloadFile() 198 | ERROR_NEEDTO_UPDATECLIENT.detail = self.tbSUpdateTotal.desc 199 | self:ErrorUpdate( ERROR_NEEDTO_UPDATECLIENT ) 200 | return 201 | end 202 | 203 | --开始对比文件列表 204 | self:StartCompareFileList() 205 | end 206 | 207 | --比较两个版本 版本格式 1.0.0.0 208 | --如果version和oldVersion相同,则返回0 209 | --如果version比oldVersion高,则返回1 210 | --如果version比oldVersion低,则返回-1 211 | function UpdateSystem:CompareVersion( version, oldVersion, len ) 212 | if version == oldVersion then return 0 end 213 | 214 | if len == nil then len = 4 end 215 | local tbV = self:GetVersionTable( version, len ) 216 | local tbOldV = self:GetVersionTable( oldVersion, len ) 217 | 218 | for i=1,len do 219 | if tbV[i] > tbOldV[i] then 220 | return 1 221 | end 222 | end 223 | 224 | return -1 225 | end 226 | 227 | function UpdateSystem:GetVersionTable( version, size ) 228 | local tb = {} 229 | local tbStr = string.split( version, "." ) 230 | for i=1,size do 231 | if tbStr[i] ~= nil then 232 | local n = tonumber( tbStr[i] ) 233 | if n ~= nil then 234 | table.insert( tb, n ) 235 | else 236 | table.insert( tb, 0 ) 237 | end 238 | else 239 | table.insert( tb, 0 ) 240 | end 241 | end 242 | return tb 243 | end 244 | 245 | function UpdateSystem:StartCompareFileList() 246 | self.bUpdateContinue = true 247 | self:LoadClientFileList() 248 | self:RequestServerFileList() 249 | --self:LoadServerFileList() 250 | end 251 | 252 | function UpdateSystem:LoadClientFileList() 253 | self:PrintLog( "读取本地文件列表" ) 254 | local data = "" 255 | local path = cc.FileUtils:getInstance():getWritablePath()..self.localFileListPath --防呆 256 | if cc.FileUtils:getInstance():isFileExist(path) then 257 | data = cc.FileUtils:getInstance():getStringFromFile( path ) 258 | else 259 | data = cc.FileUtils:getInstance():getStringFromFile( self.localFileListPath ) 260 | end 261 | self.tbCFileList = json.decode(data) 262 | end 263 | 264 | function UpdateSystem:RequestServerFileList() 265 | self:PrintLog( "正在请求服务器文件列表" ) 266 | self:HttpRequest( self.tbSUpdateTotal.url, handler(self, self.OnServerFileListResponse) ) 267 | end 268 | 269 | function UpdateSystem:OnServerFileListResponse() 270 | self:PrintLog( "收到文件列表的响应 状态:"..self.xhr.readyState ) 271 | self.xhr:unregisterScriptHandler() 272 | 273 | if self.xhr.readyState == 4 and (self.xhr.status >= 200 and self.xhr.status < 207) then 274 | local jsData = json.decode( self.xhr.response ) 275 | if self:CheckServerFileList(jsData) ~= true then 276 | self:ErrorUpdate( ERROR_SERVER_FILELIST_CONTENT ) 277 | return 278 | end 279 | 280 | self.FileDownloadURL = jsData[ "url" ] 281 | jsData[ "url" ] = nil 282 | 283 | self.tbSFileList = clone(jsData) 284 | self:GetUpdateFileList() 285 | elseif self.xhr.readyState == 1 and self.xhr.status == 0 then 286 | self:ErrorUpdate( ERROR_NETWORK ) 287 | end 288 | end 289 | 290 | function UpdateSystem:CheckServerFileList( tb ) 291 | if type(tb) ~= "table" then return false end 292 | if type(tb["url"]) ~= "string" then return false end 293 | return true 294 | end 295 | 296 | function UpdateSystem:GetUpdateFileList() 297 | self:PrintLog( "正在对比本地文件列表和服务器文件列表" ) 298 | self.tbUpdateList = {} 299 | self.CurrSize = 0 300 | self.UpdateSize = 0 301 | 302 | self:CompareList( "", self.tbSFileList, self.tbCFileList ) 303 | self.tbUpdateListTmp = clone(self.tbUpdateList) 304 | -- dump( self.UpdateSize, "UpdateSize" ) 305 | -- dump( self.tbUpdateList, "tbUpdateList" ) 306 | 307 | self:UpdateProgresses() 308 | self:DownloadNext() 309 | end 310 | 311 | function UpdateSystem:CompareList( dir, tbSList, tbCList ) 312 | if type(tbSList) ~= "table" then return end 313 | if type(tbCList) ~= "table" then return end 314 | 315 | if tbSList["MD5"] ~= nil then 316 | if tbSList["MD5"] ~= tbCList["MD5"] then 317 | self:AddFileList( dir, tbSList, self.tbUpdateList ) 318 | end 319 | return 320 | end 321 | 322 | for k,tb in pairs(tbSList) do 323 | if type(tb) == "table" then 324 | local path = dir 325 | if path ~= "" then path = path .. "/" end 326 | path = path .. k 327 | if tbCList[ k ] == nil then 328 | self:AddFileList( path, tb, self.tbUpdateList ) 329 | else 330 | self:CompareList( path, tb, tbCList[ k ] ) 331 | end 332 | end 333 | end 334 | end 335 | 336 | function UpdateSystem:AddFileList( dir, tbList, tbUpdateList ) 337 | if type(tbList) ~= "table" then return end 338 | if type(tbUpdateList) ~= "table" then return end 339 | 340 | if tbList["MD5"] ~= nil then 341 | local t = {} 342 | t.file = dir 343 | t.md5 = tbList["MD5"] or "" 344 | t.size = tbList[ "size" ] or 0 345 | table.insert( tbUpdateList, t ) 346 | if self.UpdateSize == nil then self.UpdateSize = 0 end 347 | self.UpdateSize = self.UpdateSize + t.size 348 | return 349 | end 350 | 351 | for k,tb in pairs( tbList ) do 352 | if type(tb) == "table" then 353 | local path = dir 354 | if path ~= "" then path = path .. "/" end 355 | path = path .. k 356 | self:AddFileList( path, tb, tbUpdateList ) 357 | end 358 | end 359 | end 360 | 361 | 362 | 363 | ------------下载文件------------ 364 | function UpdateSystem:DownloadNext() 365 | if self.bUpdateContinue ~= true then 366 | self:ErrorUpdate( ERROR_UPDATE_CALCEN ) 367 | return 368 | end 369 | 370 | if table.nums( self.tbUpdateList ) <= 0 then 371 | local err = self:DownloadAllDone() 372 | if type(err) == "nil" then 373 | self:UpdateDone() 374 | end 375 | else 376 | local url = self.FileDownloadURL..self.tbUpdateList[1].file 377 | self:HttpRequest( url, handler(self, self.OnServerFileResponse) ) 378 | end 379 | end 380 | 381 | function UpdateSystem:OnServerFileResponse() 382 | self:PrintLog( "收到File的响应 状态:"..self.xhr.readyState ) 383 | self.xhr:unregisterScriptHandler() 384 | 385 | if self.xhr.readyState == 1 and self.xhr.status == 0 then 386 | self:ErrorUpdate( ERROR_NETWORK ) 387 | return 388 | end 389 | 390 | if self.xhr.readyState == 4 and (self.xhr.status >= 200 and self.xhr.status < 207) then 391 | self:PrintLog( "开始校验文件内容" ) 392 | local md5code = string.upper( md5.sumhexa( self.xhr.response ) ) 393 | if md5code ~= string.upper(self.tbUpdateList[1].md5) then 394 | if not self.nFileReDownload then self.nFileReDownload = 0 end 395 | if self.nFileReDownload < 3 then 396 | self:PrintLog( "错误的文件内容,重新下载该文件" ) 397 | self.nFileReDownload = self.nFileReDownload + 1 398 | self:DownloadNext() 399 | return 400 | end 401 | 402 | self:PrintLog( "文件内容错误,停止下载" ) 403 | ERROR_DOWNLOAD_FILE_CONTENT.detail = self.tbUpdateList[1].file 404 | self:ErrorUpdate( ERROR_DOWNLOAD_FILE_CONTENT ) 405 | return 406 | end 407 | 408 | self:PrintLog( "文件内容正确" ) 409 | self.nFileReDownload = 0 410 | self:SaveDownloadFile( self.tbUpdateList[1].file, self.xhr.response ) 411 | 412 | self.CurrSize = self.CurrSize + string.len( self.xhr.response ) 413 | self:UpdateProgresses() 414 | 415 | table.remove( self.tbUpdateList, 1 ) 416 | self:DownloadNext() 417 | end 418 | end 419 | 420 | function UpdateSystem:DownloadAllDone() 421 | local err = self:ChangeAllTmpToWork() 422 | if err ~= nil then return err end 423 | 424 | err = self:RecordFileList() 425 | if err ~= nil then return err end 426 | 427 | err = self:RecordTotal() 428 | if err ~= nil then return err end 429 | 430 | return nil 431 | end 432 | 433 | function UpdateSystem:ChangeAllTmpToWork() 434 | for k,t in pairs( self.tbUpdateListTmp ) do 435 | self:ChangeOneTmpToWork( t.file ) 436 | end 437 | end 438 | 439 | function UpdateSystem:ChangeOneTmpToWork( filename ) 440 | local workname = cc.FileUtils:getInstance():getWritablePath()..filename 441 | local tmpname = workname .. DOWNLOAD_FILE_SUFFIX 442 | 443 | if cc.FileUtils:getInstance():isFileExist( tmpname ) then 444 | if cc.FileUtils:getInstance():isFileExist( workname ) then 445 | cc.FileUtils:getInstance():removeFile( workname ) 446 | end 447 | 448 | local dir = self:GetFileDirectory( tmpname ).."/" 449 | local start = string.len(dir)+1 450 | local oldname = string.sub( tmpname, start ) 451 | local name = string.sub( workname, start ) 452 | cc.FileUtils:getInstance():renameFile( dir, oldname, name ) 453 | return nil 454 | else 455 | ERROR_FILE_NOT_EXIST.detail = tmpname 456 | return ERROR_FILE_NOT_EXIST 457 | end 458 | end 459 | 460 | function UpdateSystem:RecordFileList() 461 | for k,tb in pairs( self.tbUpdateListTmp ) do 462 | if type(tb) == "table" then 463 | local tbList = self.tbCFileList 464 | local idx = string.lastindexof( tb.file, "/" ) 465 | if idx then 466 | local dir = self:GetFileDirectory( tb.file ) 467 | local tbDir = string.split( dir, "/" ) 468 | for k,d in pairs( tbDir ) do 469 | if tbList[d] == nil then 470 | tbList[d] = {} 471 | end 472 | tbList = tbList[d] 473 | end 474 | end 475 | 476 | local tf = {} 477 | tf["MD5"] = tb.md5 478 | tf["size"] = tb.size 479 | local name = tb.file 480 | if idx then name = string.sub( tb.file, idx+1 ) end 481 | tbList[name] = tf 482 | end 483 | end 484 | 485 | local path = cc.FileUtils:getInstance():getWritablePath() .. self.localFileListPath 486 | local data = json.encode(self.tbCFileList) 487 | if self:WriteFile( path, data ) == false then 488 | self:PrintLog( "文件列表更新失败" ) 489 | ERROR_RECORD_FILE_TO_FILELIST.detail = "filelist" 490 | return ERROR_RECORD_FILE_TO_FILELIST 491 | end 492 | self:PrintLog( "文件列表更新成功" ) 493 | return nil 494 | end 495 | 496 | function UpdateSystem:RecordTotal() 497 | local path = cc.FileUtils:getInstance():getWritablePath()..self.localTotalFilePath 498 | local tb = clone(self.tbSUpdateTotal) 499 | tb.url = self.tbCUpdateTotal.url 500 | if self:WriteFile( path, json.encode( tb ) ) ~= true then 501 | self:PrintLog( "[更新总文件]修改失败" ) 502 | ERROR_RECORD_TOTAL.detail = path 503 | return ERROR_RECORD_TOTAL 504 | end 505 | 506 | self:PrintLog( "[更新总文件]修改成功" ) 507 | return nil 508 | end 509 | 510 | function UpdateSystem:WriteFile( path, data ) 511 | local file = io.open( path, "wb" ) 512 | if file then 513 | io.writefile( path, data ) 514 | io.flush() 515 | io.close( file ) 516 | return true 517 | else 518 | return false 519 | end 520 | end 521 | 522 | function UpdateSystem:SaveDownloadFile( filename, data ) 523 | local dir = self:GetFileDirectory( filename ) 524 | if dir and dir ~= "" then 525 | local path = cc.FileUtils:getInstance():getWritablePath() 526 | cc.FileUtils:getInstance():createDirectory( path..dir ) 527 | end 528 | 529 | local path = cc.FileUtils:getInstance():getWritablePath()..filename..DOWNLOAD_FILE_SUFFIX 530 | if cc.FileUtils:getInstance():isFileExist( path ) then 531 | self:PrintLog( "删除本地存在的文件" ) 532 | cc.FileUtils:getInstance():removeFile( path ) 533 | end 534 | 535 | if self:WriteFile(path,data) == false then 536 | ERROR_WRITE_FILE.detail = filename 537 | self:ErrorUpdate( ERROR_WRITE_FILE ) 538 | return 539 | end 540 | 541 | self:PrintLog( "储存下载文件成功" ) 542 | end 543 | 544 | function UpdateSystem:GetFileDirectory( filename ) 545 | local idx = string.lastindexof( filename, "/" ) 546 | if idx ~= nil then 547 | return string.sub( filename, 0, idx-1 ) 548 | end 549 | return "" 550 | end 551 | 552 | 553 | 554 | ------------删除所有下载的文件------------ 555 | function UpdateSystem:RemoveAllDownloadFile() 556 | self:PrintLog( "删除所有下载的文件" ) 557 | self:LoadDownloadFileList() 558 | self:RemoveDownloadFile() 559 | self:RemoveUpdateFile() 560 | end 561 | 562 | function UpdateSystem:LoadDownloadFileList() 563 | self:PrintLog( "读取下载的文件列表" ) 564 | local data = "" 565 | local path = cc.FileUtils:getInstance():getWritablePath()..self.localFileListPath 566 | if cc.FileUtils:getInstance():isFileExist(path) then 567 | data = cc.FileUtils:getInstance():getStringFromFile( path ) 568 | local tb = json.decode(data) 569 | 570 | self.tbRemoveFileList = {} 571 | self:AddFileList( "", tb, self.tbRemoveFileList ) 572 | end 573 | end 574 | 575 | function UpdateSystem:RemoveDownloadFile() 576 | for k,tb in pairs( self.tbRemoveFileList ) do 577 | if type(tb.file) == "string" then 578 | local path = cc.FileUtils:getInstance():getWritablePath()..tb.file 579 | if cc.FileUtils:getInstance():isFileExist( path ) then 580 | cc.FileUtils:getInstance():removeFile( path ) 581 | end 582 | end 583 | end 584 | end 585 | 586 | function UpdateSystem:RemoveUpdateFile() 587 | local path = cc.FileUtils:getInstance():getWritablePath()..self.localFileListPath 588 | if cc.FileUtils:getInstance():isFileExist( path ) then 589 | cc.FileUtils:getInstance():removeFile( path ) 590 | end 591 | 592 | path = cc.FileUtils:getInstance():getWritablePath()..self.localTotalFilePath 593 | if cc.FileUtils:getInstance():isFileExist( path ) then 594 | cc.FileUtils:getInstance():removeFile( path ) 595 | end 596 | end 597 | 598 | 599 | 600 | 601 | function UpdateSystem:UpdateProgresses() 602 | if self.UpdateProgressCallback then 603 | self.UpdateProgressCallback( self.CurrSize, self.UpdateSize ) 604 | end 605 | end 606 | 607 | function UpdateSystem:UpdateDone() 608 | self:PrintLog( "更新完成" ) 609 | self:ExitUpdate() 610 | 611 | if self.UpdateDoneCallback then 612 | self.UpdateDoneCallback() 613 | end 614 | end 615 | 616 | function UpdateSystem:ErrorUpdate( err ) 617 | self:PrintLog( err.msg ) 618 | self.bUpdateContinue = false 619 | self:ExitUpdate() 620 | 621 | if self.UpdateErrorCallback then 622 | self.UpdateErrorCallback( err ) 623 | end 624 | end 625 | 626 | function UpdateSystem:ExitUpdate() 627 | if self.xhr ~= nil then 628 | self.xhr:release() 629 | self.xhr = nil 630 | end 631 | end 632 | 633 | function UpdateSystem:PrintLog( txt ) 634 | if PRINT_LOG ~= true then return end 635 | if type(txt) ~= "string" then return end 636 | 637 | if type(DEBUG) == "number" and DEBUG > 0 then 638 | print( "[UpdateSystem]:\t".. txt ) 639 | end 640 | end 641 | 642 | return UpdateSystem --------------------------------------------------------------------------------