annotate support/casc.lua @ 71:60e5f3262337 tip

Added tag v6.1.0-1 for changeset 553715eacab6
author Jerome Vuarand <jerome.vuarand@gmail.com>
date Thu, 26 Feb 2015 14:18:54 +0000
parents 8b8b0bade520
children
rev   line source
jerome@65 1 local M, plat, bin = {}, require("casc.platform"), require("casc.bin")
jerome@65 2 local jenkins96, blte = require("casc.jenkins96"), require("casc.blte")
jerome@65 3
jerome@65 4 local uint32_le, uint32_be, uint40_be, uint16_le, to_be40, to_le32, to_bin, to_hex, ssub
jerome@65 5 = bin.uint32_le, bin.uint32_be, bin.uint40_be, bin.uint16_le, bin.to_be40, bin.to_le32, bin.to_bin, bin.to_hex, string.sub
jerome@65 6
jerome@65 7 M.locale = {} do
jerome@65 8 for k,v in pairs({US=0x02, KR=0x04, FR=0x10, DE=0x20, CN=0x40, ES=0x80, TW=0x0100, GB=0x0200, MX=0x1000, RU=0x2000, BR=0x4000, IT=0x8000, PT=0x010000}) do
jerome@65 9 local isCN, m = k == "cn", v * 2
jerome@65 10 M.locale[k] = function(loc, tf)
jerome@65 11 return loc % m >= v and ((tf % 16 > 7) == isCN and 2 or 1) or nil
jerome@65 12 end
jerome@65 13 end
jerome@65 14 end
jerome@65 15 local function defaultLocale(loc, tf, cdn)
jerome@65 16 return (loc % 0x400 < 0x200 and 0 or 2) + (tf % 16 < 8 and 1 or 0) - (cdn and 4 or 0)
jerome@65 17 end
jerome@65 18
jerome@65 19 local function check_args(name, idx, v, t1, t2, ...)
jerome@65 20 if t1 then
jerome@65 21 local tt = type(v)
jerome@65 22 if tt ~= t1 and tt ~= t2 then
jerome@65 23 error('Invalid argument #' .. idx .. " to " .. name .. ": " .. t1 .. " expected; got " .. tt, 3)
jerome@65 24 end
jerome@65 25 return check_args(name, idx+1, ...)
jerome@65 26 end
jerome@65 27 end
jerome@65 28
jerome@65 29 local function readFile(...)
jerome@65 30 local path = plat.path(...)
jerome@65 31 if path then
jerome@65 32 local h, err = io.open(path, "rb")
jerome@65 33 if h then
jerome@65 34 local c = h:read("*a")
jerome@65 35 h:close()
jerome@65 36 return c
jerome@65 37 end
jerome@65 38 return h, err
jerome@65 39 end
jerome@65 40 end
jerome@65 41 local function readURL(...)
jerome@65 42 local url = plat.url(...)
jerome@65 43 if url then
jerome@65 44 return plat.http(url)
jerome@65 45 end
jerome@65 46 end
jerome@65 47 local function prefixHash(h)
jerome@65 48 local a, b, c = h:match("((%x%x)(%x%x).+)")
jerome@65 49 return b, c, a
jerome@65 50 end
jerome@65 51 local function cachedRead3(cpath, lpath, url)
jerome@65 52 local ret = cpath and readFile(cpath) or lpath and readFile(lpath)
jerome@65 53 if not ret and url then
jerome@65 54 ret = readURL(url)
jerome@65 55 if ret and cpath then
jerome@65 56 local h = io.open(cpath, "wb")
jerome@65 57 if h then
jerome@65 58 h:write(ret)
jerome@65 59 h:close()
jerome@65 60 end
jerome@65 61 end
jerome@65 62 end
jerome@65 63 return ret
jerome@65 64 end
jerome@65 65 local function cachedRead(casc, ...)
jerome@65 66 local ckey = plat.url(...)
jerome@65 67 ckey = ckey and casc.cachebase and plat.path(casc.cachebase, (ckey:gsub("[/\\]", "-")))
jerome@65 68 return cachedRead3(ckey, plat.path(casc.base, ...), plat.url(casc.cdnbase, ...))
jerome@65 69 end
jerome@65 70
jerome@65 71 local function parseInfoData(data)
jerome@65 72 local hname, htype, hn, ret, ln, last = {}, {}, 1, {}, 1
jerome@65 73 local i, s, line = data:gmatch("[^\n]+")
jerome@65 74 line = i(s, line)
jerome@65 75 for e in line:gmatch("[^|]+") do
jerome@65 76 hn, hname[hn], htype[hn] = hn + 1, e:match("^([^!]+)!([^:]+)")
jerome@65 77 end
jerome@65 78
jerome@65 79 for e in i,s,line do
jerome@65 80 local l, ln = {}, 1
jerome@65 81 for f in e:gmatch("|?([^|]*)") do
jerome@65 82 if f ~= "" then
jerome@65 83 l[hname[ln]] = htype[ln] == "DEC" and tonumber(f) or f
jerome@65 84 end
jerome@65 85 ln = ln + 1
jerome@65 86 end
jerome@65 87 if ln > 1 then
jerome@65 88 ret[#ret+1] = l
jerome@65 89 end
jerome@65 90 end
jerome@65 91
jerome@65 92 return ret
jerome@65 93 end
jerome@65 94 local function parseConfigData(data, into)
jerome@65 95 local ret, ln, last = type(into) == "table" and into or {}, 1
jerome@65 96 for l in data:gmatch("[^\r\n]+") do
jerome@65 97 if l:match("^%s*[^#].-%S") then
jerome@65 98 local name, args = l:match("^%s*(%S+)%s*=(.*)$")
jerome@65 99 if name then
jerome@65 100 last, ln = {}, 1
jerome@65 101 ret[name] = last
jerome@65 102 end
jerome@65 103 for s in (args or l):gmatch("%S+") do
jerome@65 104 last[ln], ln = s, ln + 1
jerome@65 105 end
jerome@65 106 end
jerome@65 107 end
jerome@65 108 return ret
jerome@65 109 end
jerome@65 110 local function parseLocalIndexData(data, into)
jerome@65 111 local pos, sub, len = 8 + uint32_le(data), data.sub
jerome@65 112 pos = pos + ((16 - pos % 16) % 16)
jerome@65 113 len, pos = uint32_le(data, pos), pos + 9
jerome@65 114 assert(len % 18 == 0, "Index data block length parity check")
jerome@65 115
jerome@65 116 for i=1, len/18 do
jerome@65 117 into[sub(data, pos, pos + 8)], pos = uint40_be(data, pos+8), pos + 18
jerome@65 118 end
jerome@65 119
jerome@65 120 return len/18
jerome@65 121 end
jerome@65 122 local function parseCDNIndexData(name, data, into)
jerome@65 123 local dlen, p = #data-28, 0
jerome@65 124 for i=1, math.floor(dlen/4100) - math.floor(dlen/844600) do
jerome@65 125 for pos=p, p+4072, 24 do
jerome@65 126 local len = uint32_be(data, pos+16)
jerome@65 127 if len > 0 then
jerome@65 128 local ofs = uint32_be(data, pos+20)
jerome@65 129 into[ssub(data, pos+1, pos+9)] = ofs .. "-" .. (ofs+len) .. ":" .. name
jerome@65 130 end
jerome@65 131 end
jerome@65 132 p = p + 4096
jerome@65 133 end
jerome@65 134 end
jerome@65 135 local function parseRootData(data, ed)
jerome@65 136 local ret, pos, dl = {}, 1, #data
jerome@65 137 while pos < dl do
jerome@65 138 local n, tflag, loc = uint32_le(data, pos-1), uint32_le(data, pos+3), uint32_le(data, pos+7)
jerome@65 139 pos = pos + 12 + 4*n
jerome@65 140 for i=1,n do
jerome@65 141 local nhash = ssub(data, pos+16, pos+23)
jerome@65 142 local t, j = ret[nhash] or {}
jerome@65 143 ret[nhash], j = t, #t
jerome@65 144 t[j+1], t[j+2], t[j+3], pos = ssub(data, pos,pos+15), tflag, loc, pos + 24
jerome@65 145 end
jerome@65 146 end
jerome@65 147 return ret
jerome@65 148 end
jerome@65 149 local function parseEncodingData(data)
jerome@65 150 local h, locale, n = {}, data:sub(1,2), uint32_be(data, 9)
jerome@65 151 local p2 = 22 + uint32_be(data, 18) + 32*n
jerome@65 152 for i=1, n do
jerome@65 153 local c = uint16_le(data, p2)
jerome@65 154 while c > 0 do
jerome@65 155 local chsh = ssub(data, p2+7, p2+22)
jerome@65 156 h[chsh] = {size=uint32_be(data, p2+2)}
jerome@65 157 p2 = p2 + 6+16
jerome@65 158 for i=1, c do
jerome@65 159 h[chsh][i], p2 = ssub(data, p2+1, p2+16), p2 + 16
jerome@65 160 end
jerome@65 161 c = uint16_le(data, p2)
jerome@65 162 end
jerome@65 163 p2 = data:match("()%z%Z", p2)
jerome@65 164 end
jerome@65 165 return h, locale
jerome@65 166 end
jerome@65 167
jerome@65 168 local function getFileByLoc(casc, loc, lcache)
jerome@65 169 local cnt = lcache and readFile(lcache)
jerome@65 170 if cnt then
jerome@65 171 elseif type(loc) == "number" and casc.base then
jerome@65 172 cnt = blte.readArchive(plat.path(casc.base, "data", ("data.%03d"):format(loc / 2^30)), loc % 2^30)
jerome@65 173 elseif type(loc) == "string" and casc.cdnbase then
jerome@65 174 local range, name = loc:match("(%d+%-%d+):(.+)")
jerome@65 175 cnt = blte.readData(plat.http(plat.url(casc.cdnbase, "data", prefixHash(name or loc)), range and {Range="bytes=" .. range}))
jerome@65 176 if lcache and cnt then
jerome@65 177 local h = io.open(lcache, "wb")
jerome@65 178 if h then
jerome@65 179 h:write(cnt)
jerome@65 180 h:close()
jerome@65 181 end
jerome@65 182 end
jerome@65 183 end
jerome@65 184 return cnt
jerome@65 185 end
jerome@65 186 local function getLocByFileKey(casc, fileKey)
jerome@65 187 local key, i, i2 = fileKey:sub(1,9), casc.index, casc.indexCDN
jerome@65 188 return i and i[key] or i2 and i2[key]
jerome@65 189 end
jerome@65 190 local function getFileByKey(casc, fileKey, lcache)
jerome@65 191 return getFileByLoc(casc, getLocByFileKey(casc, #fileKey == 32 and to_bin(fileKey) or fileKey), lcache)
jerome@65 192 end
jerome@65 193 local function getLocFromRootEntry(casc, e, rateFunc)
jerome@65 194 local score, ret = -math.huge
jerome@65 195 for i=1,e and #e or 0, 3 do
jerome@65 196 local keys = casc.encoding[e[i] or e]
jerome@65 197 local loc = keys and keys[1] and getLocByFileKey(casc, keys[1])
jerome@65 198 if loc then
jerome@65 199 local lscore = rateFunc(e[i+2], e[i+1], type(loc) == "string")
jerome@65 200 if lscore and lscore > score then
jerome@65 201 score, ret = lscore, loc
jerome@65 202 end
jerome@65 203 end
jerome@65 204 end
jerome@65 205 return ret
jerome@65 206 end
jerome@65 207 local function addCDNKey(casc, key)
jerome@65 208 if casc.indexCDN then
jerome@65 209 local hkey = #key == 32 and key or to_hex(key)
jerome@65 210 local rkey = #key == 16 and key or to_bin(key)
jerome@65 211 casc.indexCDN[rkey:sub(1,9)] = hkey
jerome@65 212 end
jerome@65 213 return key
jerome@65 214 end
jerome@65 215
jerome@65 216 local handle = {}
jerome@65 217 local handle_mt = {__index=handle}
jerome@65 218 function handle:readFile(path, lang)
jerome@65 219 lang = M.locale[lang] or lang
jerome@65 220 check_args("cascHandle:readFile", 1, path, "string", "string", lang, "function", "nil")
jerome@65 221
jerome@65 222 local h = jenkins96.hash_path(path)
jerome@65 223 return getFileByLoc(self, getLocFromRootEntry(self, self.root and self.root[h], lang or self.locale))
jerome@65 224 end
jerome@65 225 function handle:readFileByEncodingHash(hash)
jerome@65 226 check_args("cascHandle:readFileByEncodingHash", 1, hash, "string", "string")
jerome@65 227
jerome@65 228 return getFileByKey(self, #hash == 32 and to_bin(hash) or hash)
jerome@65 229 end
jerome@65 230 function handle:readFileByContentHash(hash)
jerome@65 231 check_args("cascHandle:readFileByContentHash", 1, hash, "string", "string")
jerome@65 232
jerome@65 233 local k = self.encoding[#hash == 32 and to_bin(hash) or hash]
jerome@65 234 return k and getFileByKey(self, k[1])
jerome@65 235 end
jerome@65 236 function handle:setLocale(locale)
jerome@65 237 check_args("cascHandle:setLocale", 1, M.locale[locale] or locale, "function", "nil")
jerome@65 238
jerome@65 239 self.locale = M.locale[locale] or locale or defaultLocale
jerome@65 240 end
jerome@65 241
jerome@65 242 function M.open(localBase, buildKey, cdnBase, cdnKey, cacheBase)
jerome@65 243 if buildKey == nil then error('Syntax: cac.open("localBase" or nil, "buildKey", "cdnBase" or nil, "cdnKey" or nil[, "cacheBase"])') end
jerome@65 244 check_args("casc.open", 1, localBase, "string", "nil", buildKey, "string", nil, cdnBase, "string", "nil", cdnKey, "string", "nil", cacheBase, "string", "nil")
jerome@65 245 assert(type(localBase) == "string" or type(cdnBase) == "string", 'invalid arguments: at least one of localBase, cdnBase must not be nil')
jerome@65 246 assert(type(cdnBase) ~= "string" or type(cdnKey) == "string", 'invalid arguments: if cdnBase is specified, cdnKey must be a string')
jerome@65 247
jerome@65 248 local casc = setmetatable({base=localBase, buildKey=buildKey, cdnbase=cdnBase, cdnKey=cdnKey, cachebase=cacheBase, locale=defaultLocale}, handle_mt)
jerome@65 249 casc.conf = parseConfigData(cachedRead(casc, "config", prefixHash(buildKey)))
jerome@65 250 if cdnKey then
jerome@65 251 parseConfigData(cachedRead(casc, "config", prefixHash(cdnKey)), casc.conf)
jerome@65 252 casc.indexCDN = {}
jerome@65 253 for k,v in pairs(casc.conf.archives) do
jerome@65 254 parseCDNIndexData(v, cachedRead3(
jerome@65 255 plat.path(cacheBase, "index." .. v),
jerome@65 256 plat.path(localBase, "indices", v .. ".index"),
jerome@65 257 plat.url(cdnBase, "data", prefixHash(v .. ".index"))
jerome@65 258 ), casc.indexCDN)
jerome@65 259 end
jerome@65 260 end
jerome@65 261 if localBase then
jerome@65 262 casc.index = {}
jerome@65 263 for _, f in plat.files(plat.path(localBase, "data"), "*.idx") do
jerome@65 264 parseLocalIndexData(assert(readFile(f)), casc.index)
jerome@65 265 end
jerome@65 266 end
jerome@65 267
jerome@65 268 local ekey = addCDNKey(casc, casc.conf.encoding[#casc.conf.encoding])
jerome@65 269 casc.encoding = parseEncodingData(getFileByKey(casc, ekey, plat.path(cacheBase, "encoding." .. ekey)))
jerome@65 270 local rkey = addCDNKey(casc, casc.encoding[to_bin(casc.conf.root[#casc.conf.root])][1])
jerome@65 271 casc.root = parseRootData(getFileByKey(casc, rkey, plat.path(cacheBase, "root." .. to_hex(rkey))))
jerome@65 272
jerome@65 273 return casc
jerome@65 274 end
jerome@65 275
jerome@65 276 function M.cdnbuild(patchBase, region)
jerome@65 277 check_args("casc.cdnbuild", 1, patchBase, "string", "string", region, "string", "nil")
jerome@65 278
jerome@65 279 local versions = parseInfoData(plat.http(plat.url(patchBase, "versions")))
jerome@65 280 local cdns = parseInfoData(plat.http(plat.url(patchBase, "cdns")))
jerome@65 281 local reginfo = {}
jerome@65 282 for i=1,#versions do
jerome@65 283 local v = versions[i]
jerome@65 284 reginfo[v.Region] = {cdnKey = v.CDNConfig, buildKey=v.BuildConfig, build=v.BuildId, version=v.VersionsName}
jerome@65 285 end
jerome@65 286 for i=1,#cdns do
jerome@65 287 local c = cdns[i]
jerome@65 288 if reginfo[c.Name] then
jerome@65 289 reginfo[c.Name].cdnBase = c.Hosts and c.Path and plat.url("http://", c.Hosts:match("%S+"), c.Path)
jerome@65 290 end
jerome@65 291 end
jerome@65 292 if reginfo[region] and reginfo[region].cdnBase then
jerome@65 293 local r = reginfo[region]
jerome@65 294 return r.buildKey, r.cdnBase, r.cdnKey, r.version, r.build
jerome@65 295 elseif region == nil then
jerome@65 296 return reginfo, versions, cdns
jerome@65 297 end
jerome@65 298 end
jerome@65 299 function M.localbuild(buildInfoPath, selectBuild)
jerome@65 300 check_args("casc.localbuild", 1, buildInfoPath, "string", "string", selectBuild, "function", "nil")
jerome@65 301
jerome@65 302 local info = parseInfoData(assert(readFile(buildInfoPath)))
jerome@65 303 if type(selectBuild) == "function" and info then
jerome@65 304 local ii = info[selectBuild(info)]
jerome@65 305 if ii then
jerome@65 306 return ii["Build Key"], plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]), ii["CDN Key"], ii["Version"]
jerome@65 307 end
jerome@65 308 elseif info then
jerome@65 309 local branches = {}
jerome@65 310 for i=1,#info do
jerome@65 311 local ii = info[i]
jerome@65 312 local curl = plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"])
jerome@65 313 branches[ii.Branch] = {cdnKey=ii["CDN Key"], buildKey=ii["Build Key"], version=ii["Version"], cdnBase=curl}
jerome@65 314 end
jerome@65 315 return branches, info
jerome@65 316 end
jerome@65 317 end
jerome@65 318
jerome@65 319 function M.selectActiveBuild(buildInfo)
jerome@65 320 check_args("casc.selectActiveBuild", 1, buildInfo, "table", "table")
jerome@65 321
jerome@65 322 for i=1,#buildInfo do
jerome@65 323 if buildInfo[i].Active == 1 then
jerome@65 324 return i
jerome@65 325 end
jerome@65 326 end
jerome@65 327 end
jerome@65 328 function M.selectUserBuild(buildInfo)
jerome@65 329 check_args("casc.selectUserBuild", 1, buildInfo, "table", "table")
jerome@65 330
jerome@65 331 print("Available builds:")
jerome@65 332 for i=1,#buildInfo do
jerome@65 333 print(("%d. [%s] %s (%s)%s"):format(i, buildInfo[i]["Build Key"], buildInfo[i].Version, buildInfo[i].Branch, buildInfo[i].Active == 1 and " (active)" or ""))
jerome@65 334 end
jerome@65 335 io.stdout:write("Enter index: ")
jerome@65 336 io.stdout:flush()
jerome@65 337
jerome@65 338 local c = tonumber(io.stdin:read("*l"))
jerome@65 339 return buildInfo[c] and c
jerome@65 340 end
jerome@65 341
jerome@65 342 return M