quick-cocos2d-x的熱更新機制實現update包(lua)(下)

原文地址:http://www.cocoachina.com/bbs/read.php?tid=213061

4.5 update.updater


這是整個更新系統的核心部分了。代碼更長一點,但其實很好懂。
在這個模塊中,我們需要完成下面的工作:
  1. 調用C++的Updater模塊來獲取遠程的版本號以及資源下載地址;
  2. 調用C++的Updater模塊來下載解壓;
  3. 合併解壓後的新資源到新資源文件夾;
  4. 更新總的資源索引;
  5. 刪除臨時文件;
  6. 報告更新中的各種錯誤。
所以說,這是一個工具模塊。它提供的是給更新使用的各種工具。而 UpdateApp 和 updateScene 則分別是功能和界面模塊。


複製代碼
  1. --- The helper for update package.
  2. -- It can download resources and uncompress it,
  3. -- copy new package to res directory,
  4. -- and remove temporery directory.
  5. -- @author zrong(zengrong.net)
  6. -- Creation 2014-07-03
  7. require "lfs"
  8. local updater = {}
  9. updater.STATES = {
  10.     kDownStart = "downloadStart",
  11.     kDownDone = "downloadDone",
  12.     kUncompressStart = "uncompressStart",
  13.     kUncompressDone = "uncompressDone",
  14.     unknown = "stateUnknown",
  15. }
  16. updater.ERRORS = {
  17.     kCreateFile = "errorCreateFile",
  18.     kNetwork = "errorNetwork",
  19.     kNoNewVersion = "errorNoNewVersion",
  20.     kUncompress = "errorUncompress",
  21.     unknown = "errorUnknown";
  22. }
  23. function updater.isState(state)
  24.     for k,v in pairs(updater.STATES) do
  25.         if v == state then
  26.             return true
  27.         end
  28.     end
  29.     return false
  30. end
  31. function updater.clone(object)
  32.     local lookup_table = {}
  33.     local function _copy(object)
  34.         if type(object) ~= "table" then
  35.             return object
  36.         elseif lookup_table[object] then
  37.             return lookup_table[object]
  38.         end
  39.         local new_table = {}
  40.         lookup_table[object] = new_table
  41.         for key, value in pairs(object) do
  42.             new_table[_copy(key)] = _copy(value)
  43.         end
  44.         return setmetatable(new_table, getmetatable(object))
  45.     end
  46.     return _copy(object)
  47. end
  48. function updater.vardump(object, label, returnTable)
  49.     local lookupTable = {}
  50.     local result = {}
  51.     local function _v(v)
  52.         if type(v) == "string" then
  53.             v = "\"" .. v .. "\""
  54.         end
  55.         return tostring(v)
  56.     end
  57.     local function _vardump(object, label, indent, nest)
  58.         label = label or ""
  59.         local postfix = ""
  60.         if nest > 1 then postfix = "," end
  61.         if type(object) ~= "table" then
  62.             if type(label) == "string" then
  63.                 result[#result +1] = string.format("%s[\"%s\"] = %s%s", indent, label, _v(object), postfix)
  64.             else
  65.                 result[#result +1] = string.format("%s%s%s", indent, _v(object), postfix)
  66.             end
  67.         elseif not lookupTable[object] then
  68.             lookupTable[object] = true
  69.             if type(label) == "string" then
  70.                 result[#result +1 ] = string.format("%s%s = {", indent, label)
  71.             else
  72.                 result[#result +1 ] = string.format("%s{", indent)
  73.             end
  74.             local indent2 = indent .. "    "
  75.             local keys = {}
  76.             local values = {}
  77.             for k, v in pairs(object) do
  78.                 keys[#keys + 1] = k
  79.                 values[k] = v
  80.             end
  81.             table.sort(keys, function(a, b)
  82.                 if type(a) == "number" and type(b) == "number" then
  83.                     return a < b
  84.                 else
  85.                     return tostring(a) < tostring(b)
  86.                 end
  87.             end)
  88.             for i, k in ipairs(keys) do
  89.                 _vardump(values[k], k, indent2, nest + 1)
  90.             end
  91.             result[#result +1] = string.format("%s}%s", indent, postfix)
  92.         end
  93.     end
  94.     _vardump(object, label, "", 1)
  95.     if returnTable then return result end
  96.     return table.concat(result, "\n")
  97. end
  98. local u  = nil
  99. local f = CCFileUtils:sharedFileUtils()
  100. -- The res index file in original package.
  101. local lresinfo = "res/resinfo.lua"
  102. local uroot = f:getWritablePath()
  103. -- The directory for save updated files.
  104. local ures = uroot.."res/"
  105. -- The package zip file what download from server.
  106. local uzip = uroot.."res.zip"
  107. -- The directory for uncompress res.zip.
  108. local utmp = uroot.."utmp/"
  109. -- The res index file in zip package for update.
  110. local zresinfo = utmp.."res/resinfo.lua"
  111. -- The res index file for final game.
  112. -- It combiled original lresinfo and zresinfo.
  113. local uresinfo = ures .. "resinfo.lua"
  114. local localResInfo = nil
  115. local remoteResInfo = nil
  116. local finalResInfo = nil
  117. local function _initUpdater()
  118.     print("initUpdater, ", u)
  119.     if not u then u = Updater:new() end
  120.     print("after initUpdater:", u)
  121. end
  122. function updater.writeFile(path, content, mode)
  123.     mode = mode or "w+b"
  124.     local file = io.open(path, mode)
  125.     if file then
  126.         if file:write(content) == nil then return false end
  127.         io.close(file)
  128.         return true
  129.     else
  130.         return false
  131.     end
  132. end
  133. function updater.readFile(path)
  134.     return f:getFileData(path)
  135. end
  136. function updater.exists(path)
  137.     return f:isFileExist(path)
  138. end
  139. --[[
  140. -- Departed, uses lfs instead.
  141. function updater._mkdir(path)
  142.     _initUpdater()
  143.     return u:createDirectory(path)
  144. end
  145. -- Departed, get a warning in ios simulator
  146. function updater._rmdir(path)
  147.     _initUpdater()
  148.     return u:removeDirectory(path)
  149. end
  150. --]]
  151. function updater.mkdir(path)
  152.     if not updater.exists(path) then
  153.         return lfs.mkdir(path)
  154.     end
  155.     return true
  156. end
  157. function updater.rmdir(path)
  158.     print("updater.rmdir:", path)
  159.     if updater.exists(path) then
  160.         local function _rmdir(path)
  161.             local iter, dir_obj = lfs.dir(path)
  162.             while true do
  163.                 local dir = iter(dir_obj)
  164.                 if dir == nil then break end
  165.                 if dir ~= "." and dir ~= ".." then
  166.                     local curDir = path..dir
  167.                     local mode = lfs.attributes(curDir, "mode")
  168.                     if mode == "directory" then
  169.                         _rmdir(curDir.."/")
  170.                     elseif mode == "file" then
  171.                         os.remove(curDir)
  172.                     end
  173.                 end
  174.             end
  175.             local succ, des = os.remove(path)
  176.             if des then print(des) end
  177.             return succ
  178.         end
  179.         _rmdir(path)
  180.     end
  181.     return true
  182. end
  183. -- Is there a update.zip package in ures directory?
  184. -- If it is true, return its abstract path.
  185. function updater.hasNewUpdatePackage()
  186.     local newUpdater = ures.."lib/update.zip"
  187.     if updater.exists(newUpdater) then
  188.         return newUpdater
  189.     end
  190.     return nil
  191. end
  192. -- Check local resinfo and remote resinfo, compare their version value.
  193. function updater.checkUpdate()
  194.     localResInfo = updater.getLocalResInfo()
  195.     local localVer = localResInfo.version
  196.     print("localVer:", localVer)
  197.     remoteResInfo = updater.getRemoteResInfo(localResInfo.update_url)
  198.     local remoteVer = remoteResInfo.version
  199.     print("remoteVer:", remoteVer)
  200.     return remoteVer ~= localVer
  201. end
  202. -- Copy resinfo.lua from original package to update directory(ures)
  203. -- when it is not in ures.
  204. function updater.getLocalResInfo()
  205.     print(string.format("updater.getLocalResInfo, lresinfo:%s, uresinfo:%s",
  206.         lresinfo,uresinfo))
  207.     local resInfoTxt = nil
  208.     if updater.exists(uresinfo) then
  209.         resInfoTxt = updater.readFile(uresinfo)
  210.     else
  211.         assert(updater.mkdir(ures), ures.." create error!")
  212.         local info = updater.readFile(lresinfo)
  213.         print("localResInfo:", info)
  214.         assert(info, string.format("Can not get the constent from %s!", lresinfo))
  215.         updater.writeFile(uresinfo, info)
  216.         resInfoTxt = info
  217.     end
  218.     return assert(loadstring(resInfoTxt))()
  219. end
  220. function updater.getRemoteResInfo(path)
  221.     _initUpdater()
  222.     print("updater.getRemoteResInfo:", path)
  223.     local resInfoTxt = u:getUpdateInfo(path)
  224.     print("resInfoTxt:", resInfoTxt)
  225.     return assert(loadstring(resInfoTxt))()
  226. end
  227. function updater.update(handler)
  228.     assert(remoteResInfo and remoteResInfo.package, "Can not get remoteResInfo!")
  229.     print("updater.update:", remoteResInfo.package)
  230.     if handler then
  231.         u:registerScriptHandler(handler)
  232.     end
  233.     updater.rmdir(utmp)
  234.     u:update(remoteResInfo.package, uzip, utmp, false)
  235. end
  236. function updater._copyNewFile(resInZip)
  237.     -- Create nonexistent directory in update res.
  238.     local i,j = 1,1
  239.     while true do
  240.         j = string.find(resInZip, "/", i)
  241.         if j == nil then break end
  242.         local dir = string.sub(resInZip, 1,j)
  243.         -- Save created directory flag to a table because
  244.         -- the io operation is too slow.
  245.         if not updater._dirList[dir] then
  246.             updater._dirList[dir] = true
  247.             local fullUDir = uroot..dir
  248.             updater.mkdir(fullUDir)
  249.         end
  250.         i = j+1
  251.     end
  252.     local fullFileInURes = uroot..resInZip
  253.     local fullFileInUTmp = utmp..resInZip
  254.     print(string.format('copy %s to %s', fullFileInUTmp, fullFileInURes))
  255.     local zipFileContent = updater.readFile(fullFileInUTmp)
  256.     if zipFileContent then
  257.         updater.writeFile(fullFileInURes, zipFileContent)
  258.         return fullFileInURes
  259.     end
  260.     return nil
  261. end
  262. function updater._copyNewFilesBatch(resType, resInfoInZip)
  263.     local resList = resInfoInZip[resType]
  264.     if not resList then return end
  265.     local finalRes = finalResInfo[resType]
  266.     for __,v in ipairs(resList) do
  267.         local fullFileInURes = updater._copyNewFile(v)
  268.         if fullFileInURes then
  269.             -- Update key and file in the finalResInfo
  270.             -- Ignores the update package because it has been in memory.
  271.             if v ~= "res/lib/update.zip" then
  272.                 finalRes[v] = fullFileInURes
  273.             end
  274.         else
  275.             print(string.format("updater ERROR, copy file %s.", v))
  276.         end
  277.     end
  278. end
  279. function updater.updateFinalResInfo()
  280.     assert(localResInfo and remoteResInfo,
  281.         "Perform updater.checkUpdate() first!")
  282.     if not finalResInfo then
  283.         finalResInfo = updater.clone(localResInfo)
  284.     end
  285.     --do return end
  286.     local resInfoTxt = updater.readFile(zresinfo)
  287.     local zipResInfo = assert(loadstring(resInfoTxt))()
  288.     if zipResInfo["version"] then
  289.         finalResInfo.version = zipResInfo["version"]
  290.     end
  291.     -- Save a dir list maked.
  292.     updater._dirList = {}
  293.     updater._copyNewFilesBatch("lib", zipResInfo)
  294.     updater._copyNewFilesBatch("oth", zipResInfo)
  295.     -- Clean dir list.
  296.     updater._dirList = nil
  297.     updater.rmdir(utmp)
  298.     local dumpTable = updater.vardump(finalResInfo, "local data", true)
  299.     dumpTable[#dumpTable+1] = "return data"
  300.     if updater.writeFile(uresinfo, table.concat(dumpTable, "\n")) then
  301.         return true
  302.     end
  303.     print(string.format("updater ERROR, write file %s.", uresinfo))
  304.     return false
  305. end
  306. function updater.getResCopy()
  307.     if finalResInfo then return updater.clone(finalResInfo) end
  308.     return updater.clone(localResInfo)
  309. end
  310. function updater.clean()
  311.     if u then
  312.         u:unregisterScriptHandler()
  313.         u:delete()
  314.         u = nil
  315.     end
  316.     updater.rmdir(utmp)
  317.     localResInfo = nil
  318.     remoteResInfo = nil
  319.     finalResInfo = nil
  320. end
  321. return updater


什麼什麼?你說有CCB和CCS?CCS你妹啊!同學我和你不是一個班的。
例如,原來在quick中這樣寫:


代碼都在上面,還是說重點:
4.5.1 就是沒有framework
我嘴巴都說出繭子了,沒有就是沒有。
不過,我又從quick CV了幾個方法過來:
  • clone 方法用來完全複製一個table,在複製文件索引列表的時候使用;
  • vardump 方法用來1持久化索引列表,使其作爲一個lua文件保存在設備存儲器上。有修改。
  • writeFile 和 readFile 用於把需要的文件寫入設備中,也用它來複制文件(讀入一個文件,在另一個地方寫入來實現複製)
  • exists 這個和quick實現的不太一樣,直接用 CCFileUtils 了。
4.5.2 文件操作
除了可以用 writeFile 和 readFile 來實現文件的複製操作之外,還要實現文件夾的創建和刪除。
這個功能可以使用 lfs(Lua file system) 來實現,參見:在lua中遞歸刪除一個文件夾 。
4.5.3 相關目錄和變量
上面的代碼中定義了幾個變量,在這裏進行介紹方便理解:
4.5.3.1 lres(local res)
安裝包所帶的res目錄;
4.5.3.2 ures(updated res)
保存在設備上的res目錄,用於保存從網上下載的新資源;
4.5.3.3 utmp(update temp)
臨時文件夾,用於解壓縮,更新後會刪除;
4.5.3.4 lresinfo(本地索引文件)
安裝包內自帶的所有資源的索引文件,所有資源路徑指向包內自帶的資源。打包的時候和產品包一起提供,產品包會默認使用這個資源索引文件來查找資源。它的大概內容如下:
                                                    local data = {    version = "1.0",    update_url = "http://192.168.18.22:8080/updater/resinfo.lua",    lib = {        ["res/lib/config.zip"] = "res/lib/config.zip",        ["res/lib/framework_precompiled.zip"] = "res/lib/framework_precompiled.zip",        ["res/lib/root.zip"] = "res/lib/root.zip",        ......    },    oth = {        ["res/pic/init_bg.png"] = "res/pic/init_bg.png",        ......    },}return data
                                                                                    
複製代碼
  1. local data = {
  2.     version = "1.0",
  3.     update_url = "http://192.168.18.22:8080/updater/resinfo.lua",
  4.     lib = {
  5.         ["res/lib/config.zip"] = "res/lib/config.zip",
  6.         ["res/lib/framework_precompiled.zip"] = "res/lib/framework_precompiled.zip",
  7.         ["res/lib/root.zip"] = "res/lib/root.zip",
  8.         ......
  9.     },
  10.     oth = {
  11.         ["res/pic/init_bg.png"] = "res/pic/init_bg.png",
  12.         ......
  13.     },
  14. }
  15. return data


從它的結構可以看出,它包含了當前包的版本(version)、在哪裏獲取要更新的資源索引文件(update_url)、當前包中所有的lua模塊的路徑(lib)、當前包中所有的資源文件的路徑(oth)。
4.5.3.5 uresinfo(更新索引文件)
保存在 ures 中的更新後的索引文件,沒有更新的資源路徑指向包內自帶的資源,更新後的資源路徑指向ures中的資源。它的內容大致如下:
config.zip 的路徑是在 iOS 模擬器中得到的。


複製代碼
  1. local data = {
  2.     version = "1.0",
  3.     update_url = "http://192.168.18.22:8080/updater/resinfo.lua",
  4.     lib = {
  5.         ["res/lib/cc.zip"] = "res/lib/cc.zip",
  6.         ["res/lib/config.zip"] = "/Users/zrong/Library/Application Support/iPhone Simulator/7.1/Applications/2B46FAC0-C419-42B5-92B0-B06DD16E113B/Documents/res/lib/config.zip",
  7.         ......
  8.     },
  9.     oth = {
  10.         ["res/pic/init_bg.png"] = "res/pic/init_bg.png",
  11.         ......
  12.     },
  13. }
  14. return data





4.5.3.6 http://192.168.18.22:8080/updater/resinfo.lua
getRemoteResInfo 方法會讀取這個文件,然後將結果解析成lua table。對比其中的version與 lrefinfo 中的區別,來決定是否需要更新。
若需要,則調用C++ Updater模塊中的方法下載 package 指定的zip包並解壓。
它的內容如下:




複製代碼
  1. local data = {
  2.     version = "1.0.2",
  3.     package = "http://192.168.18.22:8080/updater/res.zip",
  4. }
  5. return data



4.5.3.7 http://192.168.18.22:8080/updater/res.zip



zip包的文件夾結構大致如下:


複製代碼
  1. res/
  2. res/resinfo.lua
  3. res/lib/cc.zip
  4. res/pic/init_bg.png
  5. ......


zip文件的下載和解壓都是由C++完成的,但是下載和解壓的路徑需要Lua來提供。這個動作完成後,C++會通知Lua更新成功。Lua會接着進行後續操作就使用下面 4.5.4 中提到的方法來複制資源、合併 uresinfo 。


4.5.3.8 zresinfo(zip資源索引文件)


zip文件中也包含一個 resinfo.lua ,它用於指示哪些文件需要更新。內容大致如下:


複製代碼
  1. local data = {
  2.     version = "1.0.2",
  3.     lib = {
  4.         "res/lib/cc.zip",
  5.         ......
  6.     },
  7.     oth = {
  8.         "res/pic/init_bg",
  9.         ......
  10.     },
  11. }
  12. return data



這個文件中包含的所有文件必須能在zip解壓後找到。

4.5.4 update.updater.updateFinalResInfo()
這是一個至關重要的方法,讓我們代入用上面提到的變量名和目錄來描述它的功能:
它實現的功能是:
  1. 讀取 uresinfo,若沒有,則將 lresinfo 複製成 uresinfo;
  2. 從 utmp 中讀取 zresinfo,注意此時zip文件已經解壓;
  3. 將需要更新的資源文件從 utmp 中複製到 ures 中;
  4. 更新 uresinfo ,使其中的資源鍵名指向正確的資源路徑(上一步複製的目標路徑);
  5. 刪除 utmp;
  6. 將更新後的 uresinfo 作爲lua文件寫入 ures 。
4.5.5 其它方法
對 update.updater 的調用一般是這樣的順序:
  1. 調用 checkUpdat 方法檢測是否需要升級;
  2. 調用 update 方法執行升級,同時註冊事件管理handler;
  3. 升級成功,調用 getResCopy 方法獲取最新的 uresinfo 。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章