2013-10-17 21:45:23 +02:00
#!/usr/bin/env lua
-- TODO/NOTES:
2013-10-18 16:02:22 +02:00
-- 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?)
-- MAYBE/LATER:
2013-10-18 23:27:58 +02:00
-- add API calls to retrieve a list of all versions with their info (i.e., the result of getAvailableVersions)
2013-10-18 16:02:22 +02:00
-- 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, FileSize: sysup; factory, MD5: sysup; factory, ChangelogStart:, ..., ChangelogEnd:)
2013-10-17 21:45:23 +02:00
-- remove /etc/wifibox-version on macbook...
2013-10-18 16:02:22 +02:00
-- 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)
2013-10-18 23:27:58 +02:00
-- note: take care not to print any text in module functions, as this breaks http responses
2013-10-21 13:42:58 +02:00
-- change representation of sysupgrade/factory info in versionInfo? (and also in image index?) <- create api call to get all info on all versions?
2013-10-17 21:45:23 +02:00
local M = { }
2013-10-18 16:02:22 +02:00
-- NOTE: 'INSTALLED' will never be returned (and probably neither will 'INSTALLING') since in that case the device is flashing or rebooting
2013-10-18 21:46:41 +02:00
M.STATE = { NONE = 1 , DOWNLOADING = 2 , DOWNLOAD_FAILED = 3 , IMAGE_READY = 4 , INSTALLING = 5 , INSTALLED = 6 , INSTALL_FAILED = 7 }
2013-10-18 16:02:22 +02:00
M.STATE_NAMES = {
2013-10-18 21:46:41 +02:00
[ M.STATE . NONE ] = ' none ' , [ M.STATE . DOWNLOADING ] = ' downloading ' , [ M.STATE . DOWNLOAD_FAILED ] = ' download_failed ' , [ M.STATE . IMAGE_READY ] = ' image_ready ' ,
2013-10-18 16:02:22 +02:00
[ M.STATE . INSTALLING ] = ' installing ' , [ M.STATE . INSTALLED ] = ' installed ' , [ M.STATE . INSTALL_FAILED ] = ' install_failed '
}
2013-10-17 21:45:23 +02:00
M.DEFAULT_BASE_URL = ' http://doodle3d.com/updates '
2013-10-18 16:02:22 +02:00
--M.DEFAULT_BASE_URL = 'http://localhost/~USERNAME/wifibox/updates'
2013-10-17 21:45:23 +02:00
M.IMAGE_INDEX_FILE = ' wifibox-image.index '
M.CACHE_PATH = ' /tmp/d3d-updater '
2013-10-18 16:02:22 +02:00
M.STATE_FILE = ' update-state '
2013-10-17 21:45:23 +02:00
M.WGET_OPTIONS = " -q -t 1 -T 30 "
--M.WGET_OPTIONS = "-v -t 1 -T 30"
2013-10-21 12:36:54 +02:00
local verbosity = 0 -- set by parseCommandlineArguments()
2013-10-18 16:02:22 +02:00
local log = nil -- wifibox API can use M.setLogger to enable this module to use its logger
2013-10-21 12:36:54 +02:00
local useCache = true -- default, can be overwritten using M.setUseCache()
local baseUrl = M.DEFAULT_BASE_URL -- default, can be overwritten by M.setBaseUrl()
2013-10-17 21:45:23 +02:00
---------------------
-- LOCAL FUNCTIONS --
---------------------
-- use level==1 for important messages, 0 for regular messages and -1 for less important messages
2013-10-18 21:46:41 +02:00
local function P ( lvl , msg )
if log then
if lvl == - 1 then log : debug ( msg )
elseif lvl == 0 or lvl == 1 then log : info ( msg )
end
else
if ( - lvl <= verbosity ) then print ( msg ) end
end
end
local function D ( msg ) P ( - 1 , ( log and msg or " (DBG) " .. msg ) ) end
local function E ( msg )
if log then log : error ( msg )
else io.stderr : write ( msg .. ' \n ' )
end
end
2013-10-21 12:36:54 +02:00
-- splits the return status from os.execute (see: http://stackoverflow.com/questions/16158436/how-to-shift-and-mask-bits-from-integer-in-lua)
local function splitExitStatus ( exitStatus )
2013-10-21 13:42:58 +02:00
if exitStatus == - 1 then return - 1 , - 1 end
2013-10-21 12:36:54 +02:00
local cmdStatus = math.floor ( exitStatus / 256 )
local systemStatus = exitStatus - cmdStatus * 256
return cmdStatus , systemStatus
end
local function wgetStatusToString ( exitStatus )
local wgetStatus , systemStatus = splitExitStatus ( exitStatus )
if systemStatus ~= 0 then
return " interrupted: " .. systemStatus
end
2013-10-18 23:27:58 +02:00
-- adapted from man(1) wget on OSX
local statusTexts = {
[ ' 0 ' ] = ' Ok ' ,
[ ' 1 ' ] = ' Generic error ' ,
[ ' 2 ' ] = ' Parse error ' , -- for instance, when parsing command-line options, the .wgetrc or .netrc...
[ ' 3 ' ] = ' File I/O error ' ,
[ ' 4 ' ] = ' Network failure ' ,
[ ' 5 ' ] = ' SSL verification failure ' ,
[ ' 6 ' ] = ' Username/password authentication failure ' ,
[ ' 7 ' ] = ' Protocol error ' ,
[ ' 8 ' ] = ' Server issued an error response '
}
2013-10-21 12:36:54 +02:00
local result = statusTexts [ tostring ( wgetStatus ) ]
2013-10-18 23:27:58 +02:00
if result then return exitStatus .. " : " .. result
else return exitStatus
end
end
2013-10-18 21:46:41 +02:00
local function createCacheDirectory ( )
if os.execute ( ' mkdir -p ' .. M.CACHE_PATH ) ~= 0 then
return nil , " Error: could not create cache directory ' " .. M.CACHE_PATH .. " ' "
end
return true
end
2013-10-18 16:02:22 +02:00
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
2013-10-17 21:45:23 +02:00
-- trim whitespace from both ends of string (from http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76)
local function trim ( s )
if type ( s ) ~= ' string ' then return s end
return ( s : find ( ' ^%s*$ ' ) and ' ' or s : match ( ' ^%s*(.*%S) ' ) )
end
-- from utils.lua
local function readFile ( filePath , trimResult )
local f , msg , nr = io.open ( filePath , ' r ' )
if not f then return nil , msg , nr end
local res = f : read ( ' *all ' )
f : close ( )
if trimResult then
res = trim ( res )
end
return res
end
-- from utils.lua
local function exists ( file )
if not file or type ( file ) ~= ' string ' or file : len ( ) == 0 then
return nil , " file must be a non-empty string "
end
local r = io.open ( file , ' r ' ) -- ignore returned message
if r then r : close ( ) end
return r ~= nil
end
-- from utils.lua
2013-10-18 16:02:22 +02:00
--argument: either an open file or a filename
2013-10-17 21:45:23 +02:00
local function fileSize ( file )
2013-10-18 16:02:22 +02:00
local size = nil
if type ( file ) == ' file ' then
local current = file : seek ( )
size = file : seek ( ' end ' )
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
2013-10-17 21:45:23 +02:00
return size
end
-- returns return value of command
2013-10-21 12:36:54 +02:00
local function runCommand ( command , dryRun )
D ( " about to run: ' " .. command .. " ' " )
2013-10-21 13:42:58 +02:00
return ( not dryRun ) and os.execute ( command ) or - 1
2013-10-21 12:36:54 +02:00
end
local function removeFile ( filePath )
return runCommand ( ' rm ' .. filePath )
end
2013-10-17 21:45:23 +02:00
2013-10-18 21:46:41 +02:00
-- returns return value of wget (or nil if saveDir is nil or empty), filename is optional
-- NOTE: leaving out filename will cause issues with files not being overwritten but suffixed with '.1', '.2',etc instead
2013-10-17 21:45:23 +02:00
local function downloadFile ( url , saveDir , filename )
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 ' '
if filename : len ( ) > 0 then
2013-10-18 16:02:22 +02:00
return runCommand ( ' wget ' .. M.WGET_OPTIONS .. ' -O ' .. saveDir .. ' / ' .. filename .. ' ' .. url .. ' 2> /dev/null ' )
2013-10-17 21:45:23 +02:00
else
2013-10-18 16:02:22 +02:00
return runCommand ( ' wget ' .. M.WGET_OPTIONS .. ' -P ' .. saveDir .. ' ' .. url .. ' 2> /dev/null ' )
end
2013-10-17 21:45:23 +02:00
end
local function parseCommandlineArguments ( arglist )
local result = { verbosity = 0 , baseUrl = M.DEFAULT_BASE_URL , action = nil }
local nextIsVersion , nextIsUrl = false , false
for index , argument in ipairs ( arglist ) do
if nextIsVersion then
result.version = argument ; nextIsVersion = false
elseif nextIsUrl then
result.baseUrl = argument ; nextIsUrl = false
else
if argument == ' -h ' then result.action = ' showHelp '
elseif argument == ' -q ' then result.verbosity = - 1
elseif argument == ' -V ' then result.verbosity = 1
elseif argument == ' -c ' then result.useCache = true
elseif argument == ' -C ' then result.useCache = false
elseif argument == ' -u ' then nextIsUrl = true
elseif argument == ' -v ' then result.action = ' showCurrentVersion '
2013-10-18 16:02:22 +02:00
elseif argument == ' -s ' then result.action = ' showStatus '
2013-10-17 21:45:23 +02:00
elseif argument == ' -l ' then result.action = ' showAvailableVersions '
elseif argument == ' -i ' then result.action = ' showVersionInfo ' ; nextIsVersion = true
elseif argument == ' -d ' then result.action = ' imageDownload ' ; nextIsVersion = true
elseif argument == ' -f ' then result.action = ' imageInstall ' ; nextIsVersion = true
2013-10-18 16:02:22 +02:00
elseif argument == ' -r ' then result.action = ' clear '
else return nil , " unrecognized argument ' " .. argument .. " ' "
2013-10-17 21:45:23 +02:00
end
end
end
2013-10-18 16:02:22 +02:00
if result.version then
2013-10-17 21:45:23 +02:00
result.version = M.parseVersion ( result.version )
if not result.version then
return nil , " error parsing specified version "
end
end
2013-10-18 16:02:22 +02:00
if nextIsVersion then return nil , " missing required version argument " end
if nextIsUrl then return nil , " missing required URL argument " end
2013-10-17 21:45:23 +02:00
return result
end
----------------------
-- MODULE FUNCTIONS --
----------------------
2013-10-18 16:02:22 +02:00
function M . setLogger ( logger )
log = logger
end
2013-10-21 12:36:54 +02:00
function M . setUseCache ( use )
useCache = use
end
function M . setBaseUrl ( url )
baseUrl = url
end
function M . getStatus ( )
2013-10-18 21:46:41 +02:00
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
2013-10-21 13:42:58 +02:00
local unknownVersion = { major = 0 , minor = 0 , patch = 0 }
2013-10-18 16:02:22 +02:00
local result = { }
2013-10-22 03:30:23 +02:00
result.currentVersion = M.getCurrentVersion ( )
result.stateCode , result.stateText = getState ( )
result.stateCode = tonumber ( result.stateCode )
2013-10-21 12:36:54 +02:00
local verTable , msg = M.getAvailableVersions ( )
2013-10-21 13:42:58 +02:00
if not verTable then
D ( " could not obtain available versions ( " .. msg .. " ) " )
-- TODO: set an error state in result to signify we probably do not have internet access?
2013-10-22 03:30:23 +02:00
return false , result , msg
2013-10-21 13:42:58 +02:00
end
2013-10-18 21:46:41 +02:00
2013-10-21 13:42:58 +02:00
local newest = verTable and verTable [ # verTable ]
result.newestVersion = newest and newest.version or unknownVersion
2013-10-22 03:30:23 +02:00
2013-10-18 16:02:22 +02:00
if result.stateCode == M.STATE . DOWNLOADING then
result.progress = fileSize ( M.CACHE_PATH .. ' / ' .. newest.sysupgradeFilename )
2013-10-18 21:46:41 +02:00
if not result.progress then result.progress = 0 end -- in case the file does not exist yet (which yields nil)
2013-10-18 16:02:22 +02:00
result.imageSize = newest.sysupgradeFileSize
end
2013-10-22 03:30:23 +02:00
return true , result
2013-10-18 16:02:22 +02:00
end
2013-10-18 21:46:41 +02:00
-- Turns a plain-text version into a table.
-- tables as argument are ignored so you can safely pass in an already parsed
-- version and expect it back unmodified.
2013-10-17 21:45:23 +02:00
function M . parseVersion ( versionText )
2013-10-18 21:46:41 +02:00
if type ( versionText ) == ' table ' then return versionText end
2013-10-17 21:45:23 +02:00
if not versionText or versionText : len ( ) == 0 then return nil end
2013-10-18 21:46:41 +02:00
2013-10-17 21:45:23 +02:00
local major , minor , patch = versionText : match ( " ^%s*(%d+)%.(%d+)%.(%d+)%s*$ " )
if not major or not minor or not patch then return nil end
2013-10-18 21:46:41 +02:00
2013-10-17 21:45:23 +02:00
return { [ ' major ' ] = major , [ ' minor ' ] = minor , [ ' patch ' ] = patch }
end
2013-10-18 21:46:41 +02:00
-- Formats a version as returned by parseVersion().
-- Strings are returned unmodified, so an 'already formatted' version can be
-- passed in safely and expected back unmodified.
function M . formatVersion ( version )
if type ( version ) == ' string ' then return version end
return version.major .. " . " .. version.minor .. " . " .. version.patch
end
2013-10-17 21:45:23 +02:00
-- expects two tables as created by M.parseVersion()
function M . compareVersions ( versionA , versionB )
if type ( versionA ) ~= ' table ' or type ( versionB ) ~= ' table ' then return nil end
local diff = versionA.major - versionB.major
if diff == 0 then diff = versionA.minor - versionB.minor end
if diff == 0 then diff = versionA.patch - versionB.patch end
return diff > 0 and 1 or ( diff < 0 and - 1 or 0 )
end
2013-10-21 12:36:54 +02:00
-- verTable is optional, getAvailableVersions will be used to obtain it if nil
function M . findVersion ( version , verTable )
local msg = nil
2013-10-21 13:42:58 +02:00
version = M.parseVersion ( version )
2013-10-21 12:36:54 +02:00
if not verTable then verTable , msg = M.getAvailableVersions ( ) end
if not verTable then return nil , msg end
2013-10-17 21:45:23 +02:00
for _ , ent in pairs ( verTable ) do
if M.compareVersions ( ent.version , version ) == 0 then return ent end
end
2013-10-21 12:36:54 +02:00
return nil , " no such version "
2013-10-17 21:45:23 +02:00
end
-- version may be a table or a string, devtype and isFactory are optional
2013-10-18 21:46:41 +02:00
function M . constructImageFilename ( version , devType , isFactory )
2013-10-17 21:45:23 +02:00
local sf = isFactory and ' factory ' or ' sysupgrade '
2013-10-18 21:46:41 +02:00
local v = M.formatVersion ( version )
2013-10-17 21:45:23 +02:00
local dt = devType and devType or ' tl-mr3020 '
return ' doodle3d-wifibox- ' .. M.formatVersion ( v ) .. ' - ' .. dt .. ' - ' .. sf .. ' .bin '
end
2013-10-21 12:36:54 +02:00
--TODO: move up to locals
local function md5sum ( filepath )
-- TODO [osx: md5 -q <file>], [linux: ?]
end
function M . checkValidImage ( versionEntry , devType , isFactory )
local filename = M.constructImageFilename ( versionEntry.version , devType , isFactory )
--return versionEntry.md5 == md5sum(M.CACHE_PATH .. '/' .. filename)
local size = fileSize ( M.CACHE_PATH .. ' / ' .. filename )
versionEntry.isValid = versionEntry.sysupgradeFileSize == size
return versionEntry.isValid
end
2013-10-17 21:45:23 +02:00
-- returns a plain text version
function M . getCurrentVersionText ( )
local res , msg , nr = readFile ( ' /etc/wifibox-version ' , true )
if res then return res else return nil , msg , nr end
end
-- returns a table with major, minor and patch as keys
function M . getCurrentVersion ( )
2013-10-18 16:02:22 +02:00
local vt , msg = M.getCurrentVersionText ( )
2013-10-17 21:45:23 +02:00
return vt and M.parseVersion ( vt ) or nil , msg
end
2013-10-21 12:36:54 +02:00
-- returns an indexed (and sorted) table containing version tables
function M . getAvailableVersions ( )
2013-10-18 21:46:41 +02:00
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
2013-10-17 21:45:23 +02:00
local indexFilename = M.CACHE_PATH .. ' / ' .. M.IMAGE_INDEX_FILE
2013-10-18 21:46:41 +02:00
local ccRv , ccMsg = createCacheDirectory ( )
if not ccRv then return nil , ccMsg end
2013-10-17 21:45:23 +02:00
if not useCache or not exists ( indexFilename ) then
local rv = downloadFile ( baseUrl .. ' /images/ ' .. M.IMAGE_INDEX_FILE , M.CACHE_PATH , M.IMAGE_INDEX_FILE )
2013-10-18 23:27:58 +02:00
if rv ~= 0 then return nil , " could not download image index file ( " .. wgetStatusToString ( rv ) .. " ) " end
2013-10-17 21:45:23 +02:00
end
local status , idxLines = pcall ( io.lines , indexFilename )
if not status then return nil , " could not open image index file ' " .. indexFilename .. " ' " end --do not include io.lines error message
local result , entry = { } , nil
local lineno , changelogMode = 1 , false
for line in idxLines do
local k , v = line : match ( ' ^(.-):(.*)$ ' )
k , v = trim ( k ) , trim ( v )
2013-10-18 23:27:58 +02:00
if not log then D ( " # " .. lineno .. " : considering ' " .. line .. " ' ( " .. ( k or ' <nil> ' ) .. " / " .. ( v or ' <nil> ' ) .. " ) " ) end
2013-10-17 21:45:23 +02:00
if not changelogMode and ( not k or not v ) then return nil , " incorrectly formatted line in index file (line " .. lineno .. " ) " end
if k == ' ChangelogEnd ' then
changelogMode = false
elseif changelogMode then
entry.changelog = entry.changelog .. line .. ' \n '
else
if k == ' Version ' then
if entry ~= nil then table.insert ( result , entry ) end
local pv = M.parseVersion ( v )
if not pv then return nil , " incorrect version text in index file (line " .. lineno .. " ) " end
entry = { version = pv }
elseif k == ' ChangelogStart ' then
changelogMode = true
entry.changelog = " "
elseif k == ' Files ' then
local sName , fName = v : match ( ' ^(.-);(.*)$ ' )
sName , fName = trim ( sName ) , trim ( fName )
if sName then entry.sysupgradeFilename = sName end
if fName then entry.factoryFilename = fName end
elseif k == ' FileSize ' then
local sSize , fSize = v : match ( ' ^(.-);(.*)$ ' )
sSize , fSize = trim ( sSize ) , trim ( fSize )
2013-10-18 16:02:22 +02:00
if sSize then entry.sysupgradeFileSize = tonumber ( sSize ) end
if fSize then entry.factoryFileSize = tonumber ( fSize ) end
2013-10-17 21:45:23 +02:00
elseif k == ' MD5 ' then
local sSum , fSum = v : match ( ' ^(.-);(.*)$ ' )
sSum , fSum = trim ( sSum ) , trim ( fSum )
if sSum then entry.sysupgradeMD5 = sSum end
if fSum then entry.factoryMD5 = fSum end
else
P ( - 1 , " ignoring unrecognized field in index file ' " .. k .. " ' (line " .. lineno .. " ) " )
end
end
lineno = lineno + 1
end
if entry ~= nil then table.insert ( result , entry ) end
table.sort ( result , function ( a , b )
return M.compareVersions ( a.version , b.version ) < 0
end )
return result
end
2013-10-21 12:36:54 +02:00
-- devtype and isFactory are optional
2013-10-18 21:46:41 +02:00
-- returns true or nil+msg or nil + return value from wget
2013-10-21 12:36:54 +02:00
function M . downloadImageFile ( versionEntry , devType , isFactory )
2013-10-18 21:46:41 +02:00
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
2013-10-21 12:36:54 +02:00
local filename = M.constructImageFilename ( versionEntry.version , devType , isFactory )
local doDownload = not useCache
2013-10-18 16:02:22 +02:00
2013-10-18 21:46:41 +02:00
local ccRv , ccMsg = createCacheDirectory ( )
if not ccRv then return nil , ccMsg end
2013-10-21 12:36:54 +02:00
if versionEntry.isValid == false then
doDownload = true
elseif versionEntry.isValid == nil then
M.checkValidImage ( versionEntry , devType , isFactory )
if versionEntry.isValid == false then doDownload = true end
end
2013-10-18 16:02:22 +02:00
local rv = 0
if doDownload then
2013-10-22 03:30:23 +02:00
M.setState ( M.STATE . DOWNLOADING , " Downloading image ( " .. filename .. " ) " )
2013-10-18 21:46:41 +02:00
rv = downloadFile ( baseUrl .. ' /images/ ' .. filename , M.CACHE_PATH , filename )
2013-10-18 16:02:22 +02:00
end
2013-10-18 21:46:41 +02:00
if rv == 0 then
2013-10-21 12:36:54 +02:00
if M.checkValidImage ( versionEntry , devType , isFactory ) then
2013-10-22 03:30:23 +02:00
M.setState ( M.STATE . IMAGE_READY , " Image downloaded, ready to install (image name: " .. filename .. " ) " )
2013-10-21 12:36:54 +02:00
return true
else
removeFile ( M.CACHE_PATH .. ' / ' .. filename )
local ws = " Image download failed (invalid image) "
2013-10-22 03:30:23 +02:00
M.setState ( M.STATE . DOWNLOAD_FAILED , ws )
2013-10-21 12:36:54 +02:00
return nil , ws
end
2013-10-18 21:46:41 +02:00
else
2013-10-18 23:27:58 +02:00
local ws = wgetStatusToString ( rv )
2013-10-21 12:36:54 +02:00
removeFile ( M.CACHE_PATH .. ' / ' .. filename )
2013-10-22 03:30:23 +02:00
M.setState ( M.STATE . DOWNLOAD_FAILED , " Image download failed (wget error: " .. ws .. " ) " )
2013-10-18 23:27:58 +02:00
return nil , ws
2013-10-18 21:46:41 +02:00
end
2013-10-17 21:45:23 +02:00
end
2013-10-21 12:36:54 +02:00
-- this function will not return if everything goes to plan
2013-10-18 21:46:41 +02:00
-- noRetain, devType and isFactory are optional
-- returns true or nil + wget return value
2013-10-21 12:36:54 +02:00
function M . flashImageVersion ( versionEntry , noRetain , devType , isFactory )
2013-10-22 03:30:23 +02:00
log : info ( " flashImageVersion " )
2013-10-21 12:36:54 +02:00
local imgName = M.constructImageFilename ( versionEntry.version , devType , isFactory )
2013-10-17 21:45:23 +02:00
local cmd = noRetain and ' sysupgrade -n ' or ' sysupgrade '
cmd = cmd .. M.CACHE_PATH .. ' / ' .. imgName
2013-10-18 21:46:41 +02:00
local ccRv , ccMsg = createCacheDirectory ( )
if not ccRv then return nil , ccMsg end
2013-10-21 12:36:54 +02:00
if not M.checkValidImage ( versionEntry ) then
return nil , " no valid image for requested version present "
end
2013-10-22 03:30:23 +02:00
M.setState ( M.STATE . INSTALLING , " Installing new image ( " .. imgName .. " ) " ) -- yes this is rather pointless
2013-10-21 12:36:54 +02:00
local rv = runCommand ( cmd ) -- if everything goes to plan, this will not return
2013-10-18 16:02:22 +02:00
2013-10-21 12:36:54 +02:00
if rv == 0 then
2013-10-22 03:30:23 +02:00
M.setState ( M.STATE . INSTALLED , " Image installed " )
2013-10-21 12:36:54 +02:00
else
-- NOTE: if cmdrv == 127, this means the command was not found
local cmdrv , sysrv = splitExitStatus ( rv )
2013-10-22 03:30:23 +02:00
M.setState ( M.STATE . INSTALL_FAILED , " Image installation failed (sysupgrade returned " .. cmdrv .. " , execution status: " .. sysrv .. " ) " )
2013-10-18 16:02:22 +02:00
end
2013-10-18 21:46:41 +02:00
return ( rv == 0 ) and true or nil , rv
2013-10-17 21:45:23 +02:00
end
2013-10-18 21:46:41 +02:00
--returns true on success, or nil+msg otherwise
2013-10-18 16:02:22 +02:00
function M . clear ( )
2013-10-18 21:46:41 +02:00
local ccRv , ccMsg = createCacheDirectory ( )
if not ccRv then return nil , ccMsg end
2013-10-18 23:27:58 +02:00
D ( " Removing " .. M.CACHE_PATH .. " /doodle3d-wifibox-*.bin " )
2013-10-22 03:30:23 +02:00
M.setState ( M.STATE . NONE , " " )
2013-10-18 21:46:41 +02:00
local rv = os.execute ( ' rm -f ' .. M.CACHE_PATH .. ' /doodle3d-wifibox-*.bin ' )
return ( rv == 0 ) and true or nil , " could not remove image files "
2013-10-18 16:02:22 +02:00
end
2013-10-22 03:30:23 +02:00
-- NOTE: make sure the cache directory exists before calling this function or it will fail.
-- NOTE: this function _can_ fail but we don't expect this to happen so the return value is ignored for now
function M . setState ( code , msg )
local s = code .. ' | ' .. msg
D ( " set update state: " .. M.STATE_NAMES [ code ] .. " (' " .. s .. " ') " )
local file , msg = io.open ( M.CACHE_PATH .. ' / ' .. M.STATE_FILE , ' w ' )
2013-10-18 16:02:22 +02:00
2013-10-22 03:30:23 +02:00
if not file then
E ( " error: could not open state file for writing ( " .. msg .. " ) " )
return false
end
file : write ( s )
file : close ( )
return true
end
2013-10-18 16:02:22 +02:00
2013-10-17 21:45:23 +02:00
----------
-- MAIN --
----------
local function main ( )
local argTable , msg = parseCommandlineArguments ( arg )
if not argTable then
E ( " error interpreting command-line arguments, try '-h' for help ( " .. msg .. " ) " )
os.exit ( 1 )
end
2013-10-18 21:46:41 +02:00
verbosity = argTable.verbosity
2013-10-17 21:45:23 +02:00
if argTable.useCache ~= nil then useCache = argTable.useCache end
P ( 0 , " Doodle3D Wifibox firmware updater " )
2013-10-18 21:46:41 +02:00
local cacheCreated , msg = createCacheDirectory ( )
if not cacheCreated then
E ( msg )
2013-10-17 21:45:23 +02:00
os.exit ( 1 )
end
if argTable.action == ' showHelp ' then
2013-10-18 16:02:22 +02:00
P ( 1 , " \t -h \t \t Show this help message " )
P ( 1 , " \t -q \t \t quiet mode " )
P ( 1 , " \t -V \t \t verbose mode " )
P ( 1 , " \t -c \t \t Use cache as much as possible " )
P ( 1 , " \t -C \t \t Do not use the cache " )
P ( 1 , " \t -u <base_url> \t Use specified base URL (default: " .. M.DEFAULT_BASE_URL .. " ) " )
P ( 1 , " \t -v \t \t Show current image version " )
P ( 1 , " \t -s \t \t Show current update status " )
P ( 1 , " \t -l \t \t Show list of available image versions (and which one has been downloaded, if any) " )
P ( 1 , " \t -i <version> \t Show information (changelog) about the requested image version " )
P ( 1 , " \t -d <version> \t Download requested image version " )
P ( 1 , " \t -f <version> \t Flash to requested image version (by means of sysupgrade) " )
P ( 1 , " \t -r \t \t Clear downloaded images and reset state " )
2013-10-17 21:45:23 +02:00
os.exit ( 10 )
elseif argTable.action == ' showCurrentVersion ' then
local vText , msg , nr = M.getCurrentVersionText ( )
if not vText then E ( " error reading firmware version ( " .. nr .. " : " .. msg .. " ) " ) ; os.exit ( 1 ) end
local v = M.parseVersion ( vText )
if not v then E ( " error parsing version ' " .. vText .. " ' " ) ; os.exit ( 2 ) end
P ( 1 , " version: " .. M.formatVersion ( v ) )
2013-10-18 16:02:22 +02:00
elseif argTable.action == ' showStatus ' then
2013-10-21 12:36:54 +02:00
local status = M.getStatus ( )
2013-10-18 16:02:22 +02:00
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
2013-10-17 21:45:23 +02:00
elseif argTable.action == ' showAvailableVersions ' then
2013-10-21 12:36:54 +02:00
local verTable , msg = M.getAvailableVersions ( )
2013-10-17 21:45:23 +02:00
if not verTable then
E ( " error collecting version information ( " .. msg .. " ) " )
os.exit ( 2 )
end
P ( 0 , " Available versions: " )
for _ , ent in ipairs ( verTable ) do P ( 1 , M.formatVersion ( ent.version ) ) end
elseif argTable.action == ' showVersionInfo ' then
2013-10-21 12:36:54 +02:00
local vEnt , msg = M.findVersion ( argTable.version )
2013-10-17 21:45:23 +02:00
if vEnt then
P ( 0 , " Information on version: " )
2013-10-18 16:02:22 +02:00
P ( 1 , " version: \t \t " .. M.formatVersion ( vEnt.version ) )
P ( 1 , " sysupgradeFilename: \t " .. ( vEnt.sysupgradeFilename or ' - ' ) )
P ( 1 , " sysupgradeFileSize: \t " .. ( vEnt.sysupgradeFileSize or ' - ' ) )
P ( 1 , " sysupgradeMD5: \t " .. ( vEnt.sysupgradeMD5 or ' - ' ) )
P ( 1 , " factoryFilename: \t " .. ( vEnt.factoryFilename or ' - ' ) )
P ( 1 , " factoryFileSize: \t " .. ( vEnt.factoryFileSize or ' - ' ) )
P ( 1 , " factoryMD5: \t \t " .. ( vEnt.factoryMD5 or ' - ' ) )
if vEnt.changelog then
P ( 1 , " \n --- Changelog --- \n " .. vEnt.changelog .. ' --- ' )
else
P ( 1 , " changelog: \t \t - " )
end
2013-10-21 12:36:54 +02:00
elseif vEnt == false then
P ( 1 , " no such version " )
os.exit ( 4 )
elseif vEnt == nil then
E ( " error searching version index ( " .. msg .. " ) " )
os.exit ( 2 )
2013-10-17 21:45:23 +02:00
end
elseif argTable.action == ' imageDownload ' then
2013-10-21 12:36:54 +02:00
local vEnt , msg = M.findVersion ( argTable.version )
if vEnt == false then
P ( 1 , " no such version " )
os.exit ( 4 )
elseif vEnt == nil then
E ( " error searching version index ( " .. msg .. " ) " )
os.exit ( 2 )
end
local rv , msg = M.downloadImageFile ( vEnt )
2013-10-18 21:46:41 +02:00
if not rv then E ( " could not download file ( " .. msg .. " ) " )
2013-10-17 21:45:23 +02:00
else P ( 1 , " success " )
end
2013-10-21 12:36:54 +02:00
2013-10-18 16:02:22 +02:00
elseif argTable.action == ' clear ' then
2013-10-18 21:46:41 +02:00
local rv , msg = M.clear ( )
if not rv then P ( 1 , " error ( " .. msg .. " ) " )
else P ( 1 , " success " )
2013-10-18 16:02:22 +02:00
end
2013-10-18 21:46:41 +02:00
2013-10-17 21:45:23 +02:00
elseif argTable.action == ' imageInstall ' then
2013-10-21 12:36:54 +02:00
local vEnt , msg = nil , nil
vEnt , msg = M.findVersion ( argTable.version )
if vEnt == false then
P ( 1 , " no such version " )
os.exit ( 4 )
elseif vEnt == nil then
E ( " error searching version index ( " .. msg .. " ) " )
os.exit ( 2 )
end
local rv
rv , msg = M.flashImageVersion ( vEnt )
E ( " error: failed to flash image to device ( " .. msg .. " ) " )
2013-10-17 21:45:23 +02:00
os.exit ( 3 )
2013-10-18 21:46:41 +02:00
2013-10-18 16:02:22 +02:00
else
P ( 0 , " usage: d3d-updater [-hqVcCvslr] [-u base_url] [-i version] [-d version] [-f version] " )
2013-10-17 21:45:23 +02:00
end
os.exit ( 0 )
end
-- only execute the main function if an arg table is present, this enables usage both as module and as standalone script
if arg ~= nil then main ( ) end
return M