0
0
mirror of https://github.com/Doodle3D/doodle3d-firmware.git synced 2024-11-17 11:07:56 +01:00

More work on updater module (WIP): mainly added state and fixed bugs.

This commit is contained in:
Wouter R 2013-10-18 16:02:22 +02:00
parent 6225cd6062
commit b5d980c52e
4 changed files with 190 additions and 79 deletions

View File

@ -100,7 +100,7 @@ define Package/wifibox/install
$(CP) $(WIFIBOX_BASE_DIR)/util/*.lua $(1)/$(TGT_LUA_DIR_SUFFIX)/util/ $(CP) $(WIFIBOX_BASE_DIR)/util/*.lua $(1)/$(TGT_LUA_DIR_SUFFIX)/util/
$(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/d3d-updater.lua $(1)/$(TGT_LUA_DIR_SUFFIX)/script $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/d3d-updater.lua $(1)/$(TGT_LUA_DIR_SUFFIX)/script
$(LN) -s /$(TGT_LUA_DIR_SUFFIX)/script/d3d-updater.lua $(1)/bin $(LN) -s /$(TGT_LUA_DIR_SUFFIX)/script/d3d-updater.lua $(1)/bin/d3d-updater
$(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/wifibox_init $(1)/etc/init.d/wifibox # copy directly to init dir (required for post-inst enabling) $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/wifibox_init $(1)/etc/init.d/wifibox # copy directly to init dir (required for post-inst enabling)
$(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/d3dapi $(1)/$(TGT_LUA_DIR_SUFFIX)/script $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/d3dapi $(1)/$(TGT_LUA_DIR_SUFFIX)/script
$(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/signin.sh $(1)/$(TGT_LUA_DIR_SUFFIX)/script $(INSTALL_BIN) $(WIFIBOX_BASE_DIR)/script/signin.sh $(1)/$(TGT_LUA_DIR_SUFFIX)/script

1
src/FIRMWARE-VERSION Normal file
View File

@ -0,0 +1 @@
0.9.0

View File

@ -346,7 +346,7 @@ function M.disassociate()
end end
function M.getStatus() function M.getStatus()
log:info("getStatus") log:info("network:getStatus")
local file, error = io.open('/tmp/networkstatus.txt','r') local file, error = io.open('/tmp/networkstatus.txt','r')
if file == nil then if file == nil then
--log:error("Util:Access:Can't read controller file. Error: "..error) --log:error("Util:Access:Can't read controller file. Error: "..error)
@ -355,8 +355,7 @@ function M.getStatus()
local status = file:read('*a') local status = file:read('*a')
--log:info(" status: "..utils.dump(status)) --log:info(" status: "..utils.dump(status))
file:close() file:close()
local parts = {} local code, msg = string.match(status, '([^|]+)|+(.*)')
local code, msg = string.match(status, "([^|]+)|+(.*)")
--log:info(" code: "..utils.dump(code)) --log:info(" code: "..utils.dump(code))
--log:info(" msg: "..utils.dump(msg)) --log:info(" msg: "..utils.dump(msg))
return code,msg return code,msg
@ -364,7 +363,7 @@ function M.getStatus()
end end
function M.setStatus(code,msg) function M.setStatus(code,msg)
log:info("setStatus: "..code.." | "..msg) log:info("network:setStatus: "..code.." | "..msg)
local file = io.open('/tmp/networkstatus.txt','w') local file = io.open('/tmp/networkstatus.txt','w')
file:write(code.."|"..msg) file:write(code.."|"..msg)
file:flush() file:flush()

View File

@ -1,27 +1,43 @@
#!/usr/bin/env lua #!/usr/bin/env lua
-- TODO/NOTES: -- TODO/NOTES:
-- implement image removal -- M.checkValidImage(verEnt) -> doet exists+fileSize/MD5 check
-- make sure downloaded files are overwritten, and never named with '.n' suffix -- after download: (can use checkValidImage for this)
-- max 1 image tegelijk gedownload (zelfs dat is al link qua geheugengebruik? -> printen blokkeren vanaf download image?) -- - remove file on fail
-- - check size or md5 and remove file on mismatch [osx: md5 -q <file>]
-- add to status: validImage: none|<version> (can use checkValidImage for this)
-- any more TODO's across this file?
-- max 1 image tegelijk (moet api doen), en rekening houden met printbuffer (printen blokkeren?)
-- API calls to add: update/status, update/download, update/install, update/clear
-- interpret wget return values more intelligently? or add function to run integrity check on index vs actually present files? -- MAYBE/LATER:
-- after downloading anything, check whether it really exists? -- wget: add provision (in verbose mode?) to use -v instead of -q and disable output redirection
-- document index file format (Version first, then in any order: Files: sysup; factory, ChangelogStart:, ..., ChangelogEnd:) -- wget: configurable timeout?
-- can we also get rid of the .lua extension? (looks nicer on command-line) -- max cache lifetime for index file?
-- document index file format (Version first, then in any order: Files: sysup; factory, FileSize: sysup; factory, MD5: sysup; factory, ChangelogStart:, ..., ChangelogEnd:)
-- remove /etc/wifibox-version on macbook... -- remove /etc/wifibox-version on macbook...
-- perhaps create a function for each action and directly assign them in the arguments parser -- copy improved fileSize back to utils (add unit tests!)
-- create new utils usable by updater as well as api? (remove dependencies on uci and logger etc)
local M = {} local M = {}
-- NOTE: 'INSTALLED' will never be returned (and probably neither will 'INSTALLING') since in that case the device is flashing or rebooting
M.STATE = { NONE = 1, DOWNLOADING = 2, IMAGE_READY = 3, INSTALLING = 4, INSTALLED = 5, INSTALL_FAILED = 6 }
M.STATE_NAMES = {
[M.STATE.NONE] = 'none', [M.STATE.DOWNLOADING] = 'downloading', [M.STATE.IMAGE_READY] = 'image_ready',
[M.STATE.INSTALLING] = 'installing', [M.STATE.INSTALLED] = 'installed', [M.STATE.INSTALL_FAILED] = 'install_failed'
}
M.DEFAULT_BASE_URL = 'http://doodle3d.com/updates' M.DEFAULT_BASE_URL = 'http://doodle3d.com/updates'
--M.DEFAULT_BASE_URL = 'http://localhost/~wouter/wifibox/updates' --M.DEFAULT_BASE_URL = 'http://localhost/~USERNAME/wifibox/updates'
M.IMAGE_INDEX_FILE = 'wifibox-image.index' M.IMAGE_INDEX_FILE = 'wifibox-image.index'
M.CACHE_PATH = '/tmp/d3d-updater' M.CACHE_PATH = '/tmp/d3d-updater'
M.STATE_FILE = 'update-state'
M.WGET_OPTIONS = "-q -t 1 -T 30" M.WGET_OPTIONS = "-q -t 1 -T 30"
--M.WGET_OPTIONS = "-v -t 1 -T 30" --M.WGET_OPTIONS = "-v -t 1 -T 30"
M.verbosity = 0 local verbosity = 0
local log = nil -- wifibox API can use M.setLogger to enable this module to use its logger
@ -32,8 +48,26 @@ M.verbosity = 0
-- use level==1 for important messages, 0 for regular messages and -1 for less important messages -- use level==1 for important messages, 0 for regular messages and -1 for less important messages
local function P(lvl, msg) if (-lvl <= M.verbosity) then print(msg) end end local function P(lvl, msg) if (-lvl <= M.verbosity) then print(msg) end end
local function E(msg) io.stderr:write(msg .. '\n') end local function E(msg) io.stderr:write(msg .. '\n') end
local function D(msg) P(-1, "(DBG) " .. msg) end
local function getState()
local file,msg = io.open(M.CACHE_PATH .. '/' .. M.STATE_FILE, 'r')
if not file then return M.STATE.NONE,"" end
local state = file:read('*a')
file:close()
local code,msg = string.match(state, '([^|]+)|+(.*)')
return code,msg
end
local function setState(code, msg)
local s = code .. '|' .. msg
if log then log:info("update state: " .. s) else D("update state: " .. s) end
local file = io.open(M.CACHE_PATH .. '/' .. M.STATE_FILE, 'w')
file:write(s)
file:close()
end
-- trim whitespace from both ends of string (from http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76) -- trim whitespace from both ends of string (from http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76)
local function trim(s) local function trim(s)
@ -68,27 +102,37 @@ local function exists(file)
end end
-- from utils.lua -- from utils.lua
--argument: either an open file or a filename
local function fileSize(file) local function fileSize(file)
local size = nil
if type(file) == 'file' then
local current = file:seek() local current = file:seek()
local size = file:seek('end') size = file:seek('end')
file:seek('set', current) file:seek('set', current)
elseif type(file) == 'string' then
local f = io.open(file)
if f then
size = f:seek('end')
f:close()
end
end
return size return size
end end
-- returns return value of command -- returns return value of command
local function runCommand(command, dryRun) P(-1, "(DBG) about to run: '" .. command .. "'"); return (not dryRun) and os.execute(command) or 0 end local function runCommand(command, dryRun) D("about to run: '" .. command .. "'"); return (not dryRun) and os.execute(command) or 0 end
-- returns return value of wget (or nil if saveDir is nil or empty) -- returns return value of wget (or nil if saveDir is nil or empty)
local function downloadFile(url, saveDir, filename) local function downloadFile(url, saveDir, filename)
if not saveDir or saveDir:len() == 0 then return nil, "saveDir must be non-empty" end if not saveDir or saveDir:len() == 0 then return nil, "saveDir must be non-empty" end
local outArg = (filename:len() > 0) and (' -O' .. filename) or '' local outArg = (filename:len() > 0) and (' -O' .. filename) or ''
if filename:len() > 0 then if filename:len() > 0 then
--return runCommand('wget ' .. M.WGET_OPTIONS .. ' -O ' .. saveDir .. '/' .. filename .. ' ' .. url .. ' 2> /dev/null') return runCommand('wget ' .. M.WGET_OPTIONS .. ' -O ' .. saveDir .. '/' .. filename .. ' ' .. url .. ' 2> /dev/null')
return runCommand('wget ' .. M.WGET_OPTIONS .. ' -O ' .. saveDir .. '/' .. filename .. ' ' .. url)
else else
return runCommand('wget ' .. M.WGET_OPTIONS .. ' -P ' .. saveDir .. ' ' .. url .. ' 2> /dev/null') return runCommand('wget ' .. M.WGET_OPTIONS .. ' -P ' .. saveDir .. ' ' .. url .. ' 2> /dev/null')
end end
end end
local function parseCommandlineArguments(arglist) local function parseCommandlineArguments(arglist)
@ -106,20 +150,18 @@ local function parseCommandlineArguments(arglist)
elseif argument == '-c' then result.useCache = true elseif argument == '-c' then result.useCache = true
elseif argument == '-C' then result.useCache = false elseif argument == '-C' then result.useCache = false
elseif argument == '-u' then nextIsUrl = true elseif argument == '-u' then nextIsUrl = true
elseif argument == '-m' then result.machineOutput = true
elseif argument == '-v' then result.action = 'showCurrentVersion' elseif argument == '-v' then result.action = 'showCurrentVersion'
elseif argument == '-s' then result.action = 'showStatus'
elseif argument == '-l' then result.action = 'showAvailableVersions' elseif argument == '-l' then result.action = 'showAvailableVersions'
elseif argument == '-i' then result.action = 'showVersionInfo'; nextIsVersion = true elseif argument == '-i' then result.action = 'showVersionInfo'; nextIsVersion = true
elseif argument == '-d' then result.action = 'imageDownload'; nextIsVersion = true elseif argument == '-d' then result.action = 'imageDownload'; nextIsVersion = true
elseif argument == '-r' then result.action = 'imageRemove'
elseif argument == '-f' then result.action = 'imageInstall'; nextIsVersion = true elseif argument == '-f' then result.action = 'imageInstall'; nextIsVersion = true
else return nil,"Unrecognized argument '" .. argument .. "'" elseif argument == '-r' then result.action = 'clear'
else return nil,"unrecognized argument '" .. argument .. "'"
end end
end end
end end
if result.machineOutput then result.verbosity = -1 end
if result.version then if result.version then
result.version = M.parseVersion(result.version) result.version = M.parseVersion(result.version)
if not result.version then if not result.version then
@ -127,8 +169,8 @@ local function parseCommandlineArguments(arglist)
end end
end end
if nextIsVersion then return nil, "Missing required version argument" end if nextIsVersion then return nil, "missing required version argument" end
if nextIsUrl then return nil, "Missing required URL argument" end if nextIsUrl then return nil, "missing required URL argument" end
return result return result
end end
@ -140,6 +182,28 @@ end
-- MODULE FUNCTIONS -- -- MODULE FUNCTIONS --
---------------------- ----------------------
function M.setLogger(logger)
log = logger
end
function M.getStatus(baseUrl, useCache)
local result = {}
local verTable = M.getAvailableVersions(baseUrl, useCache)
local newest = verTable[#verTable]
result.currentVersion = M.getCurrentVersion()
result.newestVersion = newest.version
result.stateCode, result.stateText = getState()
result.stateCode = tonumber(result.stateCode)
if result.stateCode == M.STATE.DOWNLOADING then
result.progress = fileSize(M.CACHE_PATH .. '/' .. newest.sysupgradeFilename)
result.imageSize = newest.sysupgradeFileSize
end
return result
end
function M.parseVersion(versionText) function M.parseVersion(versionText)
if not versionText or versionText:len() == 0 then return nil end if not versionText or versionText:len() == 0 then return nil end
local major,minor,patch = versionText:match("^%s*(%d+)%.(%d+)%.(%d+)%s*$") local major,minor,patch = versionText:match("^%s*(%d+)%.(%d+)%.(%d+)%s*$")
@ -181,12 +245,12 @@ end
-- returns a table with major, minor and patch as keys -- returns a table with major, minor and patch as keys
function M.getCurrentVersion() function M.getCurrentVersion()
local vt,msg = getCurrentVersionText() local vt,msg = M.getCurrentVersionText()
return vt and M.parseVersion(vt) or nil,msg return vt and M.parseVersion(vt) or nil,msg
end end
-- requires url of image index file; returns an indexed (and sorted) table containing version tables -- requires url of image index file; returns an indexed (and sorted) table containing version tables
function M.getAvailableVersions(baseUrl, useCache) function M.getAvailableVersions(baseUrl, useCache, version)
local indexFilename = M.CACHE_PATH .. '/' .. M.IMAGE_INDEX_FILE local indexFilename = M.CACHE_PATH .. '/' .. M.IMAGE_INDEX_FILE
if not useCache or not exists(indexFilename) then if not useCache or not exists(indexFilename) then
@ -228,8 +292,8 @@ function M.getAvailableVersions(baseUrl, useCache)
elseif k == 'FileSize' then elseif k == 'FileSize' then
local sSize,fSize = v:match('^(.-);(.*)$') local sSize,fSize = v:match('^(.-);(.*)$')
sSize,fSize = trim(sSize), trim(fSize) sSize,fSize = trim(sSize), trim(fSize)
if sSize then entry.sysupgradeFileSize = sSize end if sSize then entry.sysupgradeFileSize = tonumber(sSize) end
if fSize then entry.factoryFileSize = fSize end if fSize then entry.factoryFileSize = tonumber(fSize) end
elseif k == 'MD5' then elseif k == 'MD5' then
local sSum,fSum = v:match('^(.-);(.*)$') local sSum,fSum = v:match('^(.-);(.*)$')
sSum,fSum = trim(sSum), trim(fSum) sSum,fSum = trim(sSum), trim(fSum)
@ -256,9 +320,16 @@ end
function M.downloadImageFile(baseUrl, ver, forceDownload, devType, isFactory) function M.downloadImageFile(baseUrl, ver, forceDownload, devType, isFactory)
local filename = M.constructImageFilename(ver, devType, isFactory) local filename = M.constructImageFilename(ver, devType, isFactory)
local doDownload = (type(forceDownload) == 'boolean') and forceDownload or (not exists(M.CACHE_PATH .. '/' .. filename)) local doDownload = (type(forceDownload) == 'boolean') and forceDownload or (not exists(M.CACHE_PATH .. '/' .. filename))
--TODO: if file exists but is of different length, set doDownload to true
--TODO: if file exists but does not match md5sum, set doDownload to true --TODO: call M.checkValidImage, set doDownload to true if not valid
return doDownload and downloadFile(baseUrl .. '/images/' .. filename, M.CACHE_PATH, filename) or 0
local rv = 0
if doDownload then
setState(M.STATE.DOWNLOADING, "Downloading image (" .. filename .. ")")
rv = downloadFile(baseUrl .. '/images/' .. filename, M.CACHE_PATH, filename) or 0
end
setState(M.STATE.IMAGE_READY, "Image downloaded, ready to install (image name: " .. filename .. ")")
return rv
end end
-- this function will not return -- this function will not return
@ -266,10 +337,24 @@ function M.flashImageVersion(version, noRetain, devType, isFactory)
local imgName = M.constructImageFilename(version, devType, isFactory) local imgName = M.constructImageFilename(version, devType, isFactory)
local cmd = noRetain and 'sysupgrade -n ' or 'sysupgrade ' local cmd = noRetain and 'sysupgrade -n ' or 'sysupgrade '
cmd = cmd .. M.CACHE_PATH .. '/' .. imgName cmd = cmd .. M.CACHE_PATH .. '/' .. imgName
P(1, "running command: '" .. cmd .. "'") setState(M.STATE, "Installing new image (" .. imgName .. ")") -- yes this is rather pointless
return runCommand(cmd, true) -- if everything goes to plan, this will not return local rv = runCommand(cmd, true) -- if everything goes to plan, this will not return
if rv == 0 then setState(M.STATE.INSTALLED, "Image installed")
else setState(M.STATE.INSTALL_FAILED, "Image installation failed (sysupgrade returned " .. rv .. ")")
end
return rv
end end
function M.clear()
P(0, "Removing " .. M.CACHE_PATH .. "/doodle3d-wifibox-*.bin")
setState(M.STATE.NONE, "")
return os.execute('rm -f ' .. M.CACHE_PATH .. '/doodle3d-wifibox-*.bin')
end
---------- ----------
@ -295,20 +380,19 @@ local function main()
end end
if argTable.action == 'showHelp' then if argTable.action == 'showHelp' then
print("\t-h\t\tShow this help message") P(1, "\t-h\t\tShow this help message")
print("\t-q\t\tBe more quiet") P(1, "\t-q\t\tquiet mode")
print("\t-c\t\tUse cache as much as possible") P(1, "\t-V\t\tverbose mode")
print("\t-C\t\tDo not use the cache") P(1, "\t-c\t\tUse cache as much as possible")
print("\t-q\t\tBe more quiet") P(1, "\t-C\t\tDo not use the cache")
print("\t-V\t\tBe more verbose") P(1, "\t-u <base_url>\tUse specified base URL (default: " .. M.DEFAULT_BASE_URL .. ")")
print("\t-u <base_url>\tUse specified base URL (default: " .. M.DEFAULT_BASE_URL .. ")") P(1, "\t-v\t\tShow current image version")
print("\t-m\t\tOnly print machine-readable output (implies -q)") P(1, "\t-s\t\tShow current update status")
print("\t-v\t\tShow current image version") P(1, "\t-l\t\tShow list of available image versions (and which one has been downloaded, if any)")
print("\t-l\t\tShow list of available image versions (and which one has been downloaded, if any)") P(1, "\t-i <version>\tShow information (changelog) about the requested image version")
print("\t-i <version>\tShow information (changelog) about the requested image version") P(1, "\t-d <version>\tDownload requested image version")
print("\t-d <version>\tDownload requested image version") P(1, "\t-f <version>\tFlash to requested image version (by means of sysupgrade)")
print("\t-r\t\tRemove downloaded image") P(1, "\t-r\t\tClear downloaded images and reset state")
print("\t-f <version>\tFlash to requested image version (by means of sysupgrade)")
os.exit(10) os.exit(10)
elseif argTable.action == 'showCurrentVersion' then elseif argTable.action == 'showCurrentVersion' then
@ -318,6 +402,23 @@ local function main()
if not v then E("error parsing version '" .. vText .. "'"); os.exit(2) end if not v then E("error parsing version '" .. vText .. "'"); os.exit(2) end
P(1, "version: " .. M.formatVersion(v)) P(1, "version: " .. M.formatVersion(v))
elseif argTable.action == 'showStatus' then
local status = M.getStatus(argTable.baseUrl, useCache)
P(0, "Current update status:")
P(1, " currentVersion:\t" .. (M.formatVersion(status.currentVersion) or '?'))
P(1, " newestVersion:\t" .. (M.formatVersion(status.newestVersion) or '?'))
if status.stateText and status.stateText:len() > 0 then
P(1, " state:\t\t" .. M.STATE_NAMES[status.stateCode] .. " (" .. status.stateText .. ")")
else
P(1, " state:\t\t" .. M.STATE_NAMES[status.stateCode])
end
if status.stateCode == M.STATE.DOWNLOADING then
local percent = (status.imageSize > 0) and (math.ceil(status.progress / status.imageSize * 1000) / 10) or 0
P(1, " download progress:\t" .. status.progress .. "/" .. status.imageSize .. " (" .. percent .. "%)")
end
elseif argTable.action == 'showAvailableVersions' then elseif argTable.action == 'showAvailableVersions' then
local verTable,msg = M.getAvailableVersions(argTable.baseUrl, useCache) local verTable,msg = M.getAvailableVersions(argTable.baseUrl, useCache)
if not verTable then if not verTable then
@ -339,14 +440,18 @@ local function main()
if vEnt then if vEnt then
P(0, "Information on version:") P(0, "Information on version:")
P(1, "version: " .. M.formatVersion(vEnt.version)) P(1, " version:\t\t" .. M.formatVersion(vEnt.version))
P(1, "sysupgradeFilename: " .. (vEnt.sysupgradeFilename or '<nil>')) P(1, " sysupgradeFilename:\t" .. (vEnt.sysupgradeFilename or '-'))
P(1, "factoryFilename: " .. (vEnt.factoryFilename or '<nil>')) P(1, " sysupgradeFileSize:\t" .. (vEnt.sysupgradeFileSize or '-'))
P(1, "sysupgradeFileSize: " .. (vEnt.sysupgradeFileSize or '<nil>')) P(1, " sysupgradeMD5:\t" .. (vEnt.sysupgradeMD5 or '-'))
P(1, "factoryFileSize: " .. (vEnt.factoryFileSize or '<nil>')) P(1, " factoryFilename:\t" .. (vEnt.factoryFilename or '-'))
P(1, "sysupgradeMD5: " .. (vEnt.sysupgradeMD5 or '<nil>')) P(1, " factoryFileSize:\t" .. (vEnt.factoryFileSize or '-'))
P(1, "factoryMD5: " .. (vEnt.factoryMD5 or '<nil>')) P(1, " factoryMD5:\t\t" .. (vEnt.factoryMD5 or '-'))
P(1, "changelog: " .. (vEnt.changelog or '<nil>')) if vEnt.changelog then
P(1, "\n--- Changelog ---\n" .. vEnt.changelog .. '---')
else
P(1, " changelog:\t\t-")
end
else else
P(1, "not found") P(1, "not found")
end end
@ -357,13 +462,19 @@ local function main()
if rv ~= 0 then E("could not download file (" .. rv .. ")") if rv ~= 0 then E("could not download file (" .. rv .. ")")
else P(1, "success") else P(1, "success")
end end
elseif argTable.action == 'imageRemove' then elseif argTable.action == 'clear' then
P(0, "Removing " .. M.CACHE_PATH .. "/doodle3d-wifibox-*.bin") local rv = M.clear()
--TODO: actually remove if rv ~= 0 then
P(1, "error (" .. rv .. ")")
else
P(1, "success")
end
elseif argTable.action == 'imageInstall' then elseif argTable.action == 'imageInstall' then
local rv = M.flashImageVersion(argTable.version) local rv = M.flashImageVersion(argTable.version)
E("error: flash function returned, the device should have been flashed and rebooted instead") E("error: flash function returned, the device should have been flashed and rebooted instead")
os.exit(3) os.exit(3)
else
P(0, "usage: d3d-updater [-hqVcCvslr] [-u base_url] [-i version] [-d version] [-f version]")
end end
os.exit(0) os.exit(0)