Merge branch 'develop'

This commit is contained in:
peteruithoven
2014-03-14 15:05:26 +01:00
21 changed files with 1053 additions and 443 deletions
+1 -1
View File
@@ -1 +1 @@
0.10.1
0.10.2
+14 -2
View File
@@ -19,7 +19,7 @@
-- - _description_: A descriptive text usable by API clients
-- - _min_, _max_, _regex_: optional constraints (min and max constrain value for numbers, or length for strings)
-- - _isValid_: an optional function which should return true for valid values and false for invalid ones
-- - _subSection: optional: setting name of which current value is used as the uci section where this setting should be loaded from. Otherwise it's retrieved from the generic section. Setting subsection also means it will first try to get a default from subconf_defaults, if that doesn't exsist it will use the regular default
-- - _subSection: optional: setting name of which current value is used as the uci section where this setting should be loaded from. Otherwise it's retrieved from the generic section. Setting subsection also means it will first try to get a default from subconf_defaults, if that doesn't exsist it will use the regular default
-- The configuration keys themselves document themselves rather well, hence they are not included in the generated documentation.
--
-- NOTE: the all-caps definitions should be changed into configuration keys, or moved to a better location.
@@ -106,7 +106,7 @@ M.printer_dimensions_x = {
default_delta_rostockmax = 0,
default_deltamaker = 0,
default_kossel = 0,
default_minifactory = 150,
default_minifactory = 150,
subSection = 'printer_type',
type = 'int',
description = '',
@@ -335,4 +335,16 @@ M.doodle3d_tour_enabled = {
description = 'Show tour to new users'
}
M.doodle3d_update_includeBetas = {
default = false,
type = 'bool',
description = 'Include beta releases when updating'
}
M.doodle3d_update_baseUrl = {
default = 'http://doodle3d.com/updates',
type = 'string',
description = ''
}
return M
+2 -2
View File
@@ -87,14 +87,14 @@ local function setupAutoWifiMode()
end
if connectWith then
local rv,msg = netconf.associateSsid(connectWith,nil,nil,true)
local rv,msg = netconf.associateSsid(connectWith,nil,nil)
if rv then
return true, "autowifi: associated -- client mode with ssid '" .. connectWith .. "'"
else
return nil, "autowifi: could not associate with ssid '" .. connectWith .. "' (" .. msg .. ")"
end
elseif netMode ~= 'ap' or netName ~= apSsid then
local rv,msg = netconf.setupAccessPoint(apSsid,true)
local rv,msg = netconf.setupAccessPoint(apSsid)
if rv then
return true, "autowifi: configured as access point with ssid '" .. apSsid .. "'"
else
+37 -28
View File
@@ -54,10 +54,9 @@ end
--- Switch configuration between AP and station modes
-- @param table components a table with components as keys with operations as values (add or remove)
-- @param boolean boot If true, the components have to start instead of reloaded (only needed on boot)
-- Valid components (each with add and rm operation) are: apnet, staticaddr, dhcppool, wwwredir, dnsredir, wwwcaptive, natreflect.
-- and additionally: wifiiface/add, network/reload
function M.switchConfiguration(components,boot)
function M.switchConfiguration(components)
local dirtyList = {} -- laundry list, add config/script name as key with value c (commit), r (reload) or b (both)
for k,v in pairs(components) do
@@ -75,7 +74,7 @@ function M.switchConfiguration(components,boot)
if v == 'c' or v == 'b' then M.commitComponent(k) end
end
for k,v in pairs(dirtyList) do
if v == 'r' or v == 'b' then M.reloadComponent(k, silent, boot) end
if v == 'r' or v == 'b' then M.reloadComponent(k, silent) end
end
end
@@ -84,11 +83,9 @@ function M.commitComponent(c)
uci:commit(c)
end
function M.reloadComponent(c, silent, boot)
function M.reloadComponent(c, silent)
log:info("reloading component '" .. c .. "'")
local command = 'reload'
-- if booting, the services have to be started before they can be reloaded
if boot then command = 'start' end
local cmd = '/etc/init.d/' .. c .. ' '..command
if silent ~= nil and silent then
cmd = cmd .. ' &> /dev/null'
@@ -275,20 +272,16 @@ end
-- Note: this function might belong in the wlanconfig module but that would introduce
-- a circular dependency, easiest solution is to place the function here.
-- @tparam string ssid The SSID to use for the access point.
-- @tparam boolean boot If true, the components have to start instead of reloaded (only needed on boot)
-- @return True on success or nil+msg on error.
function M.setupAccessPoint(ssid,boot)
function M.setupAccessPoint(ssid)
M.setStatus(M.CREATING,"Creating access point '"..ssid.."'...");
boot = boot or false
--boot = false
if boot then log:info(" boot mode") end
-- add access point configuration
M.switchConfiguration({apnet="add_noreload"},boot)
M.switchConfiguration({apnet="add_noreload"})
wifi.activateConfig(ssid)
-- NOTE: dnsmasq must be reloaded after network or it will be unable to serve IP addresses
M.switchConfiguration({ wifiiface="add", network="reload", staticaddr="add", dhcppool="add_noreload", wwwredir="add", dnsredir="add" },boot)
M.switchConfiguration({dhcp="reload"},boot)
M.switchConfiguration({ wifiiface="add", network="reload", staticaddr="add", dhcppool="add_noreload", wwwredir="add", dnsredir="add" })
M.switchConfiguration({dhcp="reload"})
M.setStatus(M.CREATED,"Access point created");
@@ -335,11 +328,9 @@ end
-- @tparam string ssid The SSID to associate with.
-- @tparam string passphrase The passphrase (if any) to use, may be left out if a UCI configuration exists.
-- @tparam boolean recreate If true, a new UCI configuration based on scan data will always be created, otherwise an attempt will be made to use an existing configuration.
-- @tparam boolean boot If true, the components have to start instead of reloaded (only needed on boot)
-- @return True on success or nil+msg on error.
function M.associateSsid(ssid, passphrase, recreate, boot)
function M.associateSsid(ssid, passphrase, recreate)
log:info("netconfig:associateSsid: "..(ssid or "<nil>")..", "..(recreate or "<nil>"))
if boot then log:info(" boot mode") end
M.setStatus(M.CONNECTING,"Connecting...");
-- see if previously configured network for given ssid exists
@@ -369,17 +360,32 @@ function M.associateSsid(ssid, passphrase, recreate, boot)
--M.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
--M.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wireless="reload" }
--M.switchConfiguration{ wifiiface="add", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wireless="reload" }
M.switchConfiguration({ wifiiface="add", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm" },boot)
-- check if we are actually associated
local status = wifi.getDeviceState()
if not status.ssid or status.ssid ~= ssid then
local msg = "Could not associate with network (incorrect password?)"
M.setStatus(M.CONNECTING_FAILED,msg);
return nil,msg
end
M.switchConfiguration({ wifiiface="add", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm" })
M.setStatus(M.CONNECTED,"Connected");
-- we check if we get a ssid and ip in max 5 seconds
-- if not there is probably a issue
local attemptInterval = 1
local maxAttempts = 5
local attempt = 0
local nextAttemptTime = os.time()
while true do
if os.time() > nextAttemptTime then
log:debug("associated check "..utils.dump(attempt).."/"..utils.dump(maxAttempts))
if wifi.getLocalIP() ~= nil and wifi.getDeviceState().ssid == ssid then
break
else
attempt = attempt+1
if attempt >= maxAttempts then
-- still no correct ssid; fail
local msg = "Could not associate with network (incorrect password?)"
M.setStatus(M.CONNECTING_FAILED,msg);
return false, msg
else
nextAttemptTime = os.time() + attemptInterval
end
end
end
end
-- signin to connect.doodle3d.com
local success, output = signin.signin()
@@ -388,7 +394,10 @@ function M.associateSsid(ssid, passphrase, recreate, boot)
else
log:info("Signing in failed")
end
-- report we are connected after signin attempt
M.setStatus(M.CONNECTED,"Connected");
return true
end
--- Disassociate wlan device as client from all SSID's.
+4 -4
View File
@@ -174,7 +174,6 @@ function M.removeConfig(ssid)
if s.ssid == ssid then
uci:delete('wireless', s['.name'])
rv = true
return false
end
end)
uci:commit('wireless')
@@ -229,7 +228,7 @@ function M.createConfigFromScanInfo(info, passphrase, disabled)
network = M.NET,
device = 'radio0',
ssid = info.ssid,
bssid = info.bssid,
--bssid = info.bssid,
encryption = M.mapEncryptionType(info.encryption),
mode = mode,
}
@@ -237,8 +236,9 @@ function M.createConfigFromScanInfo(info, passphrase, disabled)
apconfig.disabled = disabled ~= nil and disabled and 1 or 0
uci:foreach('wireless', 'wifi-iface', function(s)
if s.bssid == info.bssid then
log:debug("removing old wireless config for net '" .. s.ssid .. "(bssid: " .. s.bssid .. ")'")
--if s.bssid == info.bssid then
if s.ssid == info.ssid then
log:debug("removing old wireless config for net '" .. s.ssid .. "'")
uci:delete('wireless', s['.name'])
-- return false --keep looking, just in case multiple entries with this bssid exist
end
+3 -2
View File
@@ -15,12 +15,10 @@ local wifi = require('network.wlanconfig')
local accessManager = require('util.access')
local printerAPI = require('rest.api.api_printer')
local M = {
isApi = true
}
-- TODO: this function is also defined in 2 other places, combine them (and avoid require loops)
local function operationsAccessOrFail(request, response)
if not accessManager.hasControl(request.remoteAddress) then
@@ -84,6 +82,9 @@ function M._global_POST(request, response)
local substitutedSsid = wifi.getSubstitutedSsid(settings.get('network.ap.ssid'))
response:addData("substituted_ssid",substitutedSsid)
local substitutedWiFiBoxID = wifi.getSubstitutedSsid(settings.get('network.cl.wifiboxid'))
response:addData("substituted_wifiboxid",substitutedWiFiBoxID)
end
function M.all_GET(request, response)
+10 -26
View File
@@ -14,6 +14,7 @@ local printDriver = require('print3d')
local printerUtils = require('util.printer')
local printerAPI = require('rest.api.api_printer')
local wifi = require('network.wlanconfig')
local settings = require('util.settings')
local TMP_DIR = '/tmp'
local LOG_COLLECT_DIRNAME = 'wifibox-logs'
@@ -48,6 +49,10 @@ local M = {
function M._global(request, response)
response:setSuccess()
local wifiboxid = wifi.getSubstitutedSsid(settings.get('network.cl.wifiboxid'))
response:addData('wifiboxid', wifiboxid)
end
-- TODO: redirect stdout+stderr; handle errors
@@ -144,26 +149,10 @@ function M.access(request, response)
--log:info(" remoteAddress: |"..utils.dump(request.remoteAddress).."|");
--log:info(" controller: |"..utils.dump(accessManager.getController()).."|");
-- when there is a controller we check if the printer is idle,
-- if so, it should be done printing and we can clear the controller
if accessManager.getController() ~= "" then
local argId = request:get("id")
local printer,msg = printerUtils.createPrinterOrFail(argId, response)
local rv,msg = printer:getState()
if rv then
response:setSuccess()
if(state == "idle") then -- TODO: define in constants somewhere
accessManager.setController("") -- clear controller
end
else
response:setError(msg)
return false
end
end
local hasControl = accessManager.hasControl(request.remoteAddress)
-- if hasControl then log:info(" hasControl: true")
-- else log:info(" hasControl: false") end
response:setSuccess()
response:addData('has_control', hasControl)
return true
@@ -171,14 +160,10 @@ end
function M.status(request, response)
local ds = wifi.getDeviceState()
log:debug(" ssid: "..utils.dump(ds.ssid))
local rv
rv, state = printerAPI.state(request, response)
local rv, state = printerAPI.state(request, response)
if(rv == false) then return end
if(state ~= "disconnected") then
if state ~= "disconnected" and state ~= "connecting" then
rv = printerAPI.temperature(request, response)
if(rv == false) then return end
rv = printerAPI.progress(request, response)
@@ -186,7 +171,6 @@ function M.status(request, response)
rv = M.access(request, response)
if(rv == false) then return end
end
response:addData('v', 10)
end
return M
+1 -5
View File
@@ -147,7 +147,7 @@ function M.associate_POST(request, response)
response:setSuccess("wlan is trying to associate")
local rv,msg = netconf.associateSsid(argSsid, argPhrase, argRecreate,false)
local rv,msg = netconf.associateSsid(argSsid, argPhrase, argRecreate)
if rv then
log:info("associated to wifi: "..utils.dump(argSsid))
else
@@ -215,10 +215,6 @@ end
function M.alive(request, response)
response:setSuccess("alive")
local ds = wifi.getDeviceState()
log:debug(" ssid: "..utils.dump(ds.ssid))
end
return M
+14 -6
View File
@@ -27,7 +27,7 @@ end
function M.temperature(request, response)
local argId = request:get("id")
local printer,msg = printerUtils.createPrinterOrFail(argId, response)
if not printer then return false end
if not printer or not printer:hasSocket() then return false end
local temperatures,msg = printer:getTemperatures()
@@ -49,7 +49,7 @@ end
function M.progress(request, response)
local argId = request:get("id")
local printer,msg = printerUtils.createPrinterOrFail(argId, response)
if not printer then return false end
if not printer or not printer:hasSocket() then return false end
-- NOTE: despite their names, `currentLine` is still the error indicator and `bufferedLines` the message in such case.
local currentLine,bufferedLines,totalLines = printer:getProgress()
@@ -81,6 +81,14 @@ function M.state(request, response, onlyReturnState)
response:addData('state', printerState)
end
return true, printerState
elseif not printer:hasSocket() then
-- while dev is present but no server is running yet, return 'fake' connecting state
local printerState = "connecting"
if not onlyReturnState then
response:setSuccess()
response:addData('state', printerState)
end
return true, printerState
else
local rv,msg = printer:getState()
if rv then
@@ -115,7 +123,7 @@ function M.heatup_POST(request, response)
local argId = request:get("id")
local printer,msg = printerUtils.createPrinterOrFail(argId, response)
if not printer then return false end
if not printer or not printer:hasSocket() then return false end
local temperature = settings.get('printer.heatup.temperature')
local rv,msg = printer:heatup(temperature)
@@ -138,8 +146,8 @@ function M.stop_POST(request, response)
local argId = request:get("id")
local argGcode = request:get("gcode")
local printer,msg = printerUtils.createPrinterOrFail(argId, response)
if not printer then return end
if not printer or not printer:hasSocket() then return false end
if(argGcode == nil) then
argGcode = ""
end
@@ -176,7 +184,7 @@ function M.print_POST(request, response)
local argStart = utils.toboolean(request:get("start"))
local printer,msg = printerUtils.createPrinterOrFail(argId, response)
if not printer then return end
if not printer or not printer:hasSocket() then return false end
response:addData('id', argId)
+20 -10
View File
@@ -47,11 +47,13 @@ end
function M.status(request, response)
local includeBetas = settings.get('doodle3d.update.includeBetas')
local baseUrl = settings.get('doodle3d.update.baseUrl')
updater.setLogger(log)
updater.setBaseUrl(baseUrl)
updater.setUseCache(false)
local success,status,msg = updater.getStatus()
local success,status,msg = updater.getStatus(includeBetas)
--response:addData('current_version', status.currentVersion)
response:addData('current_version', updater.formatVersion(status.currentVersion))
response:addData('state_code', status.stateCode)
@@ -62,10 +64,12 @@ function M.status(request, response)
return
end
local canUpdate = updater.compareVersions(status.newestVersion, status.currentVersion) > 0
local canUpdate = updater.compareVersions(status.newestVersion, status.currentVersion, status.newestReleaseTimestamp, status.currentReleaseTimestamp) > 0
if (status.currentVersion.suffix ~= nil) and not includeBetas then canUpdate = true end -- always allow downgrade from beta to stable if !includeBetas
--response:addData('newest_version', status.newestVersion)
response:addData('newest_version', updater.formatVersion(status.newestVersion))
if status.currentReleaseTimestamp then response:addData('current_release_date', updater.formatDate(status.currentReleaseTimestamp)) end
if status.newestReleaseTimestamp then response:addData('newest_release_date', updater.formatDate(status.newestReleaseTimestamp)) end
response:addData('can_update', canUpdate)
if status.progress then response:addData('progress', status.progress) end
@@ -87,14 +91,17 @@ function M.download_POST(request, response)
-- block access to prevent potential issues with printing (e.g. out of memory)
if not operationsAccessOrFail(request, response) then return end
local includeBetas = settings.get('doodle3d.update.includeBetas')
local baseUrl = settings.get('doodle3d.update.baseUrl')
updater.setLogger(log)
updater.setBaseUrl(baseUrl)
updater.setState(updater.STATE.DOWNLOADING,"")
local vEnt, rv, msg
if not argVersion then
local success,status,msg = updater.getStatus()
local success,status,msg = updater.getStatus(includeBetas)
if not success then
updater.setState(updater.STATE.DOWNLOAD_FAILED, msg)
response:setFail(msg)
@@ -124,7 +131,7 @@ function M.download_POST(request, response)
end
end
vEnt,msg = updater.findVersion(argVersion)
vEnt,msg = updater.findVersion(argVersion, includeBetas)
if vEnt == nil then
updater.setState(updater.STATE.DOWNLOAD_FAILED, "error searching version index (" .. msg .. ")")
response:setFail("error searching version index (" .. msg .. ")")
@@ -153,9 +160,12 @@ function M.install_POST(request, response)
local argNoRetain = request:get("no_retain")
log:info("API:update/install (noRetain: "..utils.dump(argNoRetain)..")")
local noRetain = argNoRetain == 'true'
if not operationsAccessOrFail(request, response) then return end
local includeBetas = settings.get('doodle3d.update.includeBetas')
local baseUrl = settings.get('doodle3d.update.baseUrl')
updater.setBaseUrl(baseUrl)
updater.setLogger(log)
updater.setState(updater.STATE.INSTALLING,"")
@@ -163,7 +173,7 @@ function M.install_POST(request, response)
--local rv,msg = netconf.enableAccessPoint(ssid)
if not argVersion then
local success,status,msg = updater.getStatus()
local success,status,msg = updater.getStatus(includeBetas)
if not success then
updater.setState(updater.STATE.INSTALL_FAILED, msg)
response:setFail(msg)
@@ -173,7 +183,7 @@ function M.install_POST(request, response)
end
end
vEnt,msg = updater.findVersion(argVersion)
vEnt,msg = updater.findVersion(argVersion, includeBetas)
if vEnt == nil then
updater.setState(updater.STATE.INSTALL_FAILED, "error searching version index (" .. msg .. ")")
response:setFail("error searching version index (" .. msg .. ")")
+313 -107
View File
@@ -10,12 +10,12 @@
--- This script provides an interface to upgrade or downgrade the Doodle3D wifibox.
-- It can both be used as a standalone command-line tool and as a Lua library.
-- TODO/NOTES:
-- TODO/NOTES: (from old script)
-- 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:
-- MAYBE/LATER: (from old script)
-- add API calls to retrieve a list of all versions with their info (i.e., the result of getAvailableVersions)
-- 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:)
@@ -44,27 +44,30 @@ M.STATE_NAMES = {
[M.STATE.INSTALLING] = 'installing', [M.STATE.INSTALLED] = 'installed', [M.STATE.INSTALL_FAILED] = 'install_failed'
}
--- The base URL to use for finding update files.
-- This URL will usually contain both an OpenWRT feed directory and an `images`-directory.
-- This script uses only the latter, and expects to find the file @{IMAGE_INDEX_FILE} there.
--- The default base URL to use for finding update files.
-- This URL will usually contain both an OpenWRT feed directory and an `images` directory.
-- This script uses only the latter, and expects to find the files @{IMAGE_STABLE_INDEX_FILE} and @{IMAGE_BETA_INDEX_FILE} there.
M.DEFAULT_BASE_URL = 'http://doodle3d.com/updates'
--M.DEFAULT_BASE_URL = 'http://localhost/~USERNAME/wifibox/updates'
--- The index file containing metadata on update images.
M.IMAGE_INDEX_FILE = 'wifibox-image.index'
--- The index file containing metadata on stable update images.
M.IMAGE_STABLE_INDEX_FILE = 'wifibox-image.index'
--- The index file containing metadata on beta update images.
M.IMAGE_BETA_INDEX_FILE = 'wifibox-image.beta.index'
--- Path to the updater cache.
M.CACHE_PATH = '/tmp/d3d-updater'
M.DEFAULT_CACHE_PATH = '/tmp/d3d-updater'
--- Name of the file to store current state in, this file resides in @{CACHE_PATH}.
--- Name of the file to store current state in, this file resides in @{cachePath}.
M.STATE_FILE = 'update-state'
M.WGET_OPTIONS = "-q -t 1 -T 30"
--M.WGET_OPTIONS = "-v -t 1 -T 30"
local verbosity = 0 -- set by parseCommandlineArguments()
local verbosity = 0 -- set by parseCommandlineArguments() or @{setVerbosity}
local log = nil -- wifibox API can use M.setLogger to enable this module to use its logger
local useCache = true -- default, can be overwritten using M.setUseCache()
local useCache = false -- default, can be overwritten using @{setUseCache}
local cachePath = M.DEFAULT_CACHE_PATH -- default, can be change using @{setCachePath}
local baseUrl = M.DEFAULT_BASE_URL -- default, can be overwritten by M.setBaseUrl()
@@ -101,7 +104,7 @@ local function E(msg)
end
end
--- Splits the return status from `os.execute`, which consists of two bytes.
--- Splits the return status from `os.execute` (only Lua <= 5.1), which consists of two bytes.
--
-- `os.execute` internally calls [system](http://linux.die.net/man/3/system),
-- which usually returns the command exit status as high byte (see [WEXITSTATUS](http://linux.die.net/man/2/wait)).
@@ -120,11 +123,12 @@ end
-- @number exitStatus An exit status from wget.
-- @treturn string|number Either the status followed by a description, or a message indicating the call was interrupted, or just the status if it was not recognized.
local function wgetStatusToString(exitStatus)
local wgetStatus,systemStatus = splitExitStatus(exitStatus)
-- local wgetStatus,systemStatus = splitExitStatus(exitStatus)
local wgetStatus = exitStatus
if systemStatus ~= 0 then
return "interrupted:" .. systemStatus
end
-- if systemStatus ~= 0 then
-- return "interrupted: " .. systemStatus
-- end
-- adapted from man(1) wget on OSX
local statusTexts = {
@@ -149,8 +153,9 @@ end
-- @return bool|nil True, or nil on error.
-- @return ?string A message in case of error.
local function createCacheDirectory()
if os.execute('mkdir -p ' .. M.CACHE_PATH) ~= 0 then
return nil,"Error: could not create cache directory '" .. M.CACHE_PATH .. "'"
local _,rv = M.compatexecute('mkdir -p ' .. cachePath)
if rv ~= 0 then
return nil,"Error: could not create cache directory '" .. cachePath .. "'"
end
return true
end
@@ -159,7 +164,7 @@ end
-- @treturn STATE The current state code (@{STATE}.NONE if no state has been set).
-- @treturn string The current state message (empty string if no state has been set).
local function getState()
local file,msg = io.open(M.CACHE_PATH .. '/' .. M.STATE_FILE, 'r')
local file,msg = io.open(cachePath .. '/' .. M.STATE_FILE, 'r')
if not file then return M.STATE.NONE,"" end
local state = file:read('*a')
@@ -179,7 +184,7 @@ end
--- Read the contents of a file.
--
-- TODO: this file has been copied from @{util.utils}.lua and should be merged again.
-- TODO: this file has been copied from @{util.utils}.lua and should be merged back.
-- @string filePath The file to read.
-- @bool trimResult Whether or not to trim the read data.
-- @treturn bool|nil True, or nil on error.
@@ -201,7 +206,7 @@ end
--- Reports whether or not a file exists.
--
-- TODO: this file has been copied from @{util.utils}.lua and should be merged again.
-- TODO: this file has been copied from @{util.utils}.lua and should be merged back.
-- @string file The file to report about.
-- @treturn bool True if the file exists, false otherwise.
local function exists(file)
@@ -216,7 +221,7 @@ end
--- Reports the size of a file or file handle.
--
-- TODO: this file has been copied from @{util.utils}.lua and should be merged again.
-- TODO: this file has been copied from @{util.utils}.lua and should be merged back.
-- @param file A file path or open file handle.
-- @treturn number Size of the file.
local function fileSize(file)
@@ -241,8 +246,9 @@ end
-- @bool dryRun Only log a message if true, otherwise run the command and log a message.
-- @treturn number Exit status of of command or -1 if dryRun is true.
local function runCommand(command, dryRun)
D("about to run: '" .. command .. "'")
return (not dryRun) and os.execute(command) or -1
--D("about to run: '" .. command .. "'")
if dryRun then return -1 end
return M.compatexecute(command)
end
--- Removes a file.
@@ -260,6 +266,7 @@ end
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 ''
D("Downloading file '" .. url .. "'")
if filename:len() > 0 then
return runCommand('wget ' .. M.WGET_OPTIONS .. ' -O ' .. saveDir .. '/' .. filename .. ' ' .. url .. ' 2> /dev/null')
else
@@ -269,38 +276,39 @@ end
--- Parses command-line arguments and returns a table containing information distilled from them.
-- @tparam table arglist A table in the same form as the [arg table](http://www.lua.org/pil/1.4.html) created by Lua.
-- @tparam table defaults A table with defaults settings (actually the basis for the returned table)
-- @treturn table|nil A table containing information on what to do, or nil if invalid arguments were specified.
-- @treturn ?string Descriptive message on error.
local function parseCommandlineArguments(arglist)
local result = { verbosity = 0, baseUrl = M.DEFAULT_BASE_URL, action = nil }
local function parseCommandlineArguments(arglist, defaults)
local nextIsVersion, nextIsUrl = false, false
for index,argument in ipairs(arglist) do
if nextIsVersion then
result.version = argument; nextIsVersion = false
defaults.version = argument; nextIsVersion = false
elseif nextIsUrl then
result.baseUrl = argument; nextIsUrl = false
defaults.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
if argument == '-h' then defaults.action = 'showHelp'
elseif argument == '-q' then defaults.verbosity = -1
elseif argument == '-V' then defaults.verbosity = 1
elseif argument == '-c' then defaults.useCache = true
elseif argument == '-C' then defaults.useCache = false
elseif argument == '-u' then nextIsUrl = true
elseif argument == '-v' then result.action = 'showCurrentVersion'
elseif argument == '-s' then result.action = 'showStatus'
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
elseif argument == '-r' then result.action = 'clear'
elseif argument == '-b' then defaults.includeBetas = true
elseif argument == '-v' then defaults.action = 'showCurrentVersion'
elseif argument == '-s' then defaults.action = 'showStatus'
elseif argument == '-l' then defaults.action = 'showAvailableVersions'
elseif argument == '-i' then defaults.action = 'showVersionInfo'; nextIsVersion = true
elseif argument == '-d' then defaults.action = 'imageDownload'; nextIsVersion = true
elseif argument == '-f' then defaults.action = 'imageInstall'; nextIsVersion = true
elseif argument == '-r' then defaults.action = 'clear'
else return nil,"unrecognized argument '" .. argument .. "'"
end
end
end
if result.version then
result.version = M.parseVersion(result.version)
if not result.version then
if defaults.version then
defaults.version = M.parseVersion(defaults.version)
if not defaults.version then
return nil,"error parsing specified version"
end
end
@@ -308,7 +316,23 @@ local function parseCommandlineArguments(arglist)
if nextIsVersion then return nil, "missing required version argument" end
if nextIsUrl then return nil, "missing required URL argument" end
return result
return defaults
end
--- Determines if the system is OpenWrt or not by checking if `/etc/openwrt_release` exists.
-- @treturn bool True if the OS is OpenWrt.
local function isOpenWrt()
local flag = nil
return function()
if flag == nil then
local relFile = io.open('/etc/openwrt_release', 'r')
flag = not not relFile
if relFile then relFile:close() end
return flag
else
return flag
end
end
end
--- Returns the [MD5](http://en.wikipedia.org/wiki/MD5) hash for a given file.
@@ -317,10 +341,23 @@ end
-- @string filepath The path of which to calculate the MD5-sum.
-- @treturn nil
local function md5sum(filepath)
return nil
-- TODO [osx: md5 -q <file>], [linux: ?]
end
local sfile
if not isOpenWrt() then
sfile = io.popen('md5 -q "' .. filepath .. '"')
else
sfile = io.popen('md5sum "' .. filepath .. '" 2>/dev/null', 'r')
end
local sum = sfile:read('*all')
sfile:close()
if not sum then return nil,"could not obtain MD5 sum" end
sum = sum:match('[%da-fA-F]+')
return sum
end
@@ -328,6 +365,31 @@ end
-- MODULE FUNCTIONS --
----------------------
local compatlua51 = _VERSION == 'Lua 5.1'
--- execute a shell command. Taken from penlight library.
-- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2
-- @param cmd a shell command
-- @return true if successful
-- @return actual return code
function M.compatexecute(cmd)
local res1,res2,res3 = os.execute(cmd)
if compatlua51 then
local cmd, sys = splitExitStatus(res1)
return (res1 == 0) and true,cmd or nil,cmd
else
return res1, res3
end
end
--- Set verbosity (log level) that determines which messages do get logged and which do not.
-- @tparam number level The level to set, between -1 and 1.
function M.setVerbosity(level)
if level and level >= -1 and level <= 1 then
verbosity = level
end
end
--- Enables use of the given @{util.logger} object, otherwise `stdout`/`stderr` will be used.
-- @tparam util.logger logger The logger to log future messages to.
function M.setLogger(logger)
@@ -348,16 +410,23 @@ function M.setBaseUrl(url)
baseUrl = url
end
--- Sets the filesystem path to use as cache for downloaded index and image files.
-- @string path The path to use, use nil to restore default @{DEFAULT_CACHE_PATH}.
function M.setCachePath(path)
cachePath = path or M.DEFAULT_CACHE_PATH
end
--- Returns a table with information about current update status of the wifibox.
--
-- The result table will contain at least the current version, current state code and text.
-- If the box has internet access, it will also include the newest version available.
-- If an image is currently being downloaded, progress information will also be included.
--
-- @tparam bool[opt] withBetas Consider beta releases when looking for newest version.
-- @treturn bool True if status has been determined fully, false if not.
-- @treturn table The result table.
-- @treturn ?string Descriptive message in case the result table is not complete.
function M.getStatus()
function M.getStatus(withBetas)
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
local unknownVersion = { major = 0, minor = 0, patch = 0 }
local result = {}
@@ -366,18 +435,39 @@ function M.getStatus()
result.stateCode, result.stateText = getState()
result.stateCode = tonumber(result.stateCode)
local verTable,msg = M.getAvailableVersions()
local verTable,msg = M.getAvailableVersions(withBetas and 'both' or 'stables')
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?
D("error: could not obtain available versions (" .. msg .. ")")
return false, result, msg
end
-- NOTE: to look up the current version we need a table containing all versions
local allVersionsTable,msg
if not withBetas then
allVersionsTable,msg = M.getAvailableVersions('both')
if not allVersionsTable then
D("error: could not obtain available versions including betas (" .. msg .. ")")
return false, result, msg
end
else
allVersionsTable = verTable
end
local newest = verTable and verTable[#verTable]
result.newestVersion = newest and newest.version or unknownVersion
result.newestReleaseTimestamp = newest and newest.timestamp
-- look up timestamp of current version
local cEnt = M.findVersion(result.currentVersion, nil, allVersionsTable)
if cEnt then
result.currentReleaseTimestamp = cEnt.timestamp
else
D("warning: could not find current wifibox version in release index, beta setting disabled after having beta installed?")
end
if result.stateCode == M.STATE.DOWNLOADING then
result.progress = fileSize(M.CACHE_PATH .. '/' .. newest.sysupgradeFilename)
result.progress = fileSize(cachePath .. '/' .. newest.sysupgradeFilename)
if not result.progress then result.progress = 0 end -- in case the file does not exist yet (which yields nil)
result.imageSize = newest.sysupgradeFileSize
end
@@ -387,55 +477,107 @@ end
--- Turns a plain-text version as returned by @{formatVersion} into a table.
-- @tparam string|table versionText The version string to parse, if it is already a table, it is returned as-is.
-- @treturn table A parse version.
-- @treturn table A parsed version or nil on incorrect argument.
function M.parseVersion(versionText)
if not versionText then return nil end
if type(versionText) == 'table' then return versionText end
if not versionText or versionText:len() == 0 then return nil end
local major,minor,patch = versionText:match("^%s*(%d+)%.(%d+)%.(%d+)%s*$")
if not major or not minor or not patch then return nil end
local major,minor,patch,suffix = versionText:match("^%s*(%d+)%.(%d+)%.(%d+)(-?%w*)%s*$")
if not major or not minor or not patch then return nil end -- suffix not required
return { ['major'] = major, ['minor'] = minor, ['patch'] = patch }
if type(suffix) == 'string' and suffix:len() > 0 then
if suffix:sub(1, 1) ~= '-' then return nil end
suffix = suffix:sub(2)
else
suffix = nil
end
return { ['major'] = major, ['minor'] = minor, ['patch'] = patch, ['suffix'] = suffix }
end
--- Formats a version as returned by @{parseVersion}.
-- @tparam table|string version The version to format, if it is already a string, that will be returned unmodified.
-- @treturn string A formatted version.
-- @treturn string A formatted version or nil on incorrect argument.
function M.formatVersion(version)
if not version then return nil end
if type(version) == 'string' then return version end
return version.major .. "." .. version.minor .. "." .. version.patch
local ver = version.major .. "." .. version.minor .. "." .. version.patch
if version.suffix then ver = ver .. '-' .. version.suffix end
return ver
end
--- Compares two versions.
--- Compares two versions. Note that the second return value must be used for equality testing.
-- If given, the timestamps have higher priority than the versions. Suffixes are ignored.
-- @tparam table versionA A version as returned by @{parseVersion}.
-- @tparam table versionB A version as returned by @{parseVersion}.
-- @treturn number -1 if versionA is smaller than versionB, 0 if versions are equal or 1 if versionA is larger than versionB.
function M.compareVersions(versionA, versionB)
-- @param timestampA[opt] A timestamp as returned by @{parseDate}.
-- @param timestampB[opt] A timestamp as returned by @{parseDate}.
-- @treturn number -1 if versionA/timestampA is smaller/older than versionB/timestampB, 0 if versions are equal (or undecided) or 1 if A is larger/newer than B.
-- @treturn bool True if versions are really equal (first return value can be 0 if everything but the suffix is equal)
function M.compareVersions(versionA, versionB, timestampA, timestampB)
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)
local diff = 0
if timestampA and timestampB then diff = timestampA - timestampB end
if diff == 0 then
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
end
local result = diff > 0 and 1 or (diff < 0 and -1 or 0)
local reallyEqual = (diff == 0) and (versionA.suffix == versionB.suffix)
return result, (reallyEqual and true or false)
end
--- Checks if versions are exactly equal.
-- It returns the second return value of @{compareVersions} and accepts the same arguments.
-- @treturn bool True if versions are equal, false otherwise.
function M.versionsEqual(versionA, versionB, timestampA, timestampB)
return select(2, M.compareVersions(versionA, versionB, timestampA, timestampB))
end
--- Returns information on a version if it can be found in a collection of versions as returned by @{getAvailableVersions}.
-- @tparam table version The version to look for.
-- @tparam bool[opt] withBetas If verTable is not given, download versions including beta releases
-- @tparam table[opt] verTable A table containing a collection of versions, if not passed in, it will be obtained using @{getAvailableVersions}.
-- @param timestamp[opt] Specific timestamp to look for.
-- @treturn table|nil Version information table found in the collection, or nil on error or if not found.
-- @treturn string Descriptive message in case of error or if the version could not be found.
function M.findVersion(version, verTable)
function M.findVersion(version, withBetas, verTable, timestamp)
local msg = nil
version = M.parseVersion(version)
if not verTable then verTable,msg = M.getAvailableVersions() end
if not verTable then verTable,msg = M.getAvailableVersions(withBetas and 'both' or 'stables') end
if not verTable then return nil,msg end
for _,ent in pairs(verTable) do
if M.compareVersions(ent.version, version) == 0 then return ent end
if M.versionsEqual(ent.version, version, ent.timestamp, timestamp) == true then return ent end
end
return nil,"no such version"
end
--- Turns a date of the format 'yyyymmdd' into a timestamp as returned by os.time.
-- @tparam string dateText The date to parse.
-- @return A timestamp or nil if the argument does not have correct format.
function M.parseDate(dateText)
if type(dateText) ~= 'string' or dateText:len() ~= 8 or dateText:find('[^%d]') ~= nil then return nil end
return os.time({ year = dateText:sub(1, 4), month = dateText:sub(5, 6), day = dateText:sub(7,8) })
end
--- Formats a timestamp as returned by os.time to a date of the form 'yyyymmdd'.
-- @param timestamp The timestamp to format.
-- @return A formatted date or nil if the argument is nil.
function M.formatDate(timestamp)
if not timestamp then return nil end
return os.date('%Y%m%d', timestamp)
end
--- Creates an image file name based on given properties.
-- The generated name has the following form: `doodle3d-wifibox-<version>-<deviceType>-<'factory'|'sysupgrade'>.bin`.
-- @tparam table|string version The version of the image.
@@ -449,7 +591,7 @@ function M.constructImageFilename(version, devType, isFactory)
return 'doodle3d-wifibox-' .. M.formatVersion(v) .. '-' .. dt .. '-' .. sf .. '.bin'
end
--- Checks whether a valid image file is present in @{CACHE_PATH} for the given image properties.
--- Checks whether a valid image file is present in @{cachePath} for the given image properties.
-- The versionEntry table will be augmented with an `isValid` key.
--
-- NOTE: currently, this function only checks the image exists and has the correct size.
@@ -459,12 +601,20 @@ end
-- @string[opt] devType Image device type, see @{constructImageFilename}.
-- @bool[opt] isFactory Image type, see @{constructImageFilename}.
-- @treturn bool True if a valid image is present, false otherwise.
-- @treturn string|nil Reason for being invalid if first return value is false.
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
local entSize = isFactory and versionEntry.factoryFileSize or versionEntry.sysupgradeFileSize
local entMd5 = isFactory and versionEntry.factoryMd5 or versionEntry.sysupgradeMD5
versionEntry.isValid = entMd5 == md5sum(cachePath .. '/' .. filename)
if not versionEntry.isValid then return false,"incorrect MD5 checksum" end
versionEntry.isValid = entSize == fileSize(cachePath .. '/' .. filename)
if not versionEntry.isValid then return false,"incorrect file size" end
return true
end
--- Returns the current wifibox version text, extracted from `/etc/wifibox-version`.
@@ -482,18 +632,14 @@ function M.getCurrentVersion()
end
--- Returns an indexed and sorted table containing version information tables.
-- The information is obtained from the either cached or downloaded image index (@{IMAGE_INDEX_FILE}).
-- @treturn table A table with a collection of version information tables.
function M.getAvailableVersions()
-- The information is obtained from the either cached or downloaded image index file.
local function fetchIndexTable(indexFile, cachePath)
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
local indexFilename = M.CACHE_PATH .. '/' .. M.IMAGE_INDEX_FILE
local ccRv,ccMsg = createCacheDirectory()
if not ccRv then return nil,ccMsg end
local indexFilename = cachePath .. '/' .. indexFile
if not useCache or not exists(indexFilename) then
local rv = downloadFile(baseUrl .. '/images/' .. M.IMAGE_INDEX_FILE, M.CACHE_PATH, M.IMAGE_INDEX_FILE)
if rv ~= 0 then return nil,"could not download image index file (" .. wgetStatusToString(rv) .. ")" end
local rv1,rv2 = downloadFile(baseUrl .. '/images/' .. indexFile, cachePath, indexFile)
if not rv1 then return nil,"could not download image index file (" .. wgetStatusToString(rv2) .. ")" end
end
local status,idxLines = pcall(io.lines, indexFilename)
@@ -505,7 +651,7 @@ function M.getAvailableVersions()
for line in idxLines do
local k,v = line:match('^(.-):(.*)$')
k,v = trim(k), trim(v)
if not log then D("#" .. lineno .. ": considering '" .. line .. "' (" .. (k or '<nil>') .. " / " .. (v or '<nil>') .. ")") end
--if not log then D("#" .. lineno .. ": considering '" .. line .. "' (" .. (k or '<nil>') .. " / " .. (v or '<nil>') .. ")") end
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
@@ -537,6 +683,13 @@ function M.getAvailableVersions()
sSum,fSum = trim(sSum), trim(fSum)
if sSum then entry.sysupgradeMD5 = sSum end
if fSum then entry.factoryMD5 = fSum end
elseif k == 'ReleaseDate' then
local ts = M.parseDate(v)
if not ts then
P(0, "ignoring incorrectly formatted ReleaseDate field (line " .. lineno .. ")")
else
entry.timestamp = ts
end
else
P(-1, "ignoring unrecognized field in index file '" .. k .. "' (line " .. lineno .. ")")
end
@@ -553,6 +706,36 @@ function M.getAvailableVersions()
return result
end
--- Returns an indexed and sorted table containing version information tables.
-- The information is obtained from the either cached or downloaded image index (@{IMAGE_STABLE_INDEX_FILE}).
-- @tparam which[opt] Which type of versions to fetch, either 'stables' (default), 'betas' or both.
-- @treturn table A table with a collection of version information tables.
function M.getAvailableVersions(which)
local ccRv,ccMsg = createCacheDirectory()
if not ccRv then return nil,ccMsg end
local verTable, msg = {}, nil
if which == 'stables' or which == 'both' then
verTable,msg = fetchIndexTable(M.IMAGE_STABLE_INDEX_FILE, cachePath)
if not verTable then return nil,msg end
end
if which == 'betas' or which == 'both' then
local betas,msg = fetchIndexTable(M.IMAGE_BETA_INDEX_FILE, cachePath)
if not betas then return nil,msg end
for k,v in pairs(betas) do verTable[k] = v end
end
table.sort(verTable, function(a, b)
return M.compareVersions(a.version, b.version, a.timestamp, b.timestamp) < 0
end)
return verTable
end
--- Attempts to download an image file with the requested properties.
-- @tparam table versionEntry A version information table.
-- @string[opt] devType Image device type, see @{constructImageFilename}.
@@ -574,25 +757,26 @@ function M.downloadImageFile(versionEntry, devType, isFactory)
if versionEntry.isValid == false then doDownload = true end
end
local rv = 0
local rv1,rv2 = 0,0
if doDownload then
M.setState(M.STATE.DOWNLOADING, "Downloading image (" .. filename .. ")")
rv = downloadFile(baseUrl .. '/images/' .. filename, M.CACHE_PATH, filename)
rv1,rv2 = downloadFile(baseUrl .. '/images/' .. filename, cachePath, filename)
end
if rv == 0 then
if M.checkValidImage(versionEntry, devType, isFactory) then
if rv1 then
local valid,msg = M.checkValidImage(versionEntry, devType, isFactory)
if valid then
M.setState(M.STATE.IMAGE_READY, "Image downloaded, ready to install (image name: " .. filename .. ")")
return true
else
removeFile(M.CACHE_PATH .. '/' .. filename)
local ws = "Image download failed (invalid image)"
removeFile(cachePath .. '/' .. filename)
local ws = "Image download failed (invalid image: " .. msg .. ")"
M.setState(M.STATE.DOWNLOAD_FAILED, ws)
return nil,ws
end
else
local ws = wgetStatusToString(rv)
removeFile(M.CACHE_PATH .. '/' .. filename)
local ws = wgetStatusToString(rv2)
removeFile(cachePath .. '/' .. filename)
M.setState(M.STATE.DOWNLOAD_FAILED, "Image download failed (wget error: " .. ws .. ")")
return nil,ws
end
@@ -608,10 +792,10 @@ end
-- @treturn bool|nil True on success (with the 'exception' as noted above) or nil on error.
-- @treturn ?string|number (optional) Descriptive message or sysupgrade exit status on error.
function M.flashImageVersion(versionEntry, noRetain, devType, isFactory)
log:info("flashImageVersion")
if log then log:info("flashImageVersion") end
local imgName = M.constructImageFilename(versionEntry.version, devType, isFactory)
local cmd = noRetain and 'sysupgrade -n ' or 'sysupgrade '
cmd = cmd .. M.CACHE_PATH .. '/' .. imgName
cmd = cmd .. cachePath .. '/' .. imgName
local ccRv,ccMsg = createCacheDirectory()
if not ccRv then return nil,ccMsg end
@@ -634,22 +818,30 @@ function M.flashImageVersion(versionEntry, noRetain, devType, isFactory)
return (rv == 0) and true or nil,rv
end
--- Clears '*.bin' in the @{CACHE_PATH} directory.
--- Clears '*.bin' and both index files in the @{cachePath} directory.
-- @treturn bool|nil True on success, or nil on error.
-- @treturn ?string Descriptive message on error.
function M.clear()
local ccRv,ccMsg = createCacheDirectory()
if not ccRv then return nil,ccMsg end
D("Removing " .. M.CACHE_PATH .. "/doodle3d-wifibox-*.bin")
D("Removing " .. cachePath .. "/doodle3d-wifibox-*.bin")
M.setState(M.STATE.NONE, "")
local rv = os.execute('rm -f ' .. M.CACHE_PATH .. '/doodle3d-wifibox-*.bin')
return (rv == 0) and true or nil,"could not remove image files"
local success = true
local rv = M.compatexecute('rm -f ' .. cachePath .. '/doodle3d-wifibox-*.bin')
success = success and (rv == 0)
local rv = M.compatexecute('rm -f ' .. cachePath .. '/' .. M.IMAGE_STABLE_INDEX_FILE)
success = success and (rv == 0)
local rv = M.compatexecute('rm -f ' .. cachePath .. '/' .. M.IMAGE_BETA_INDEX_FILE)
success = success and (rv == 0)
--return success,"could not delete all files"
return true
end
--- Set updater state.
--
-- NOTE: make sure the cache directory @{CACHE_PATH} exists before calling this function or it will fail.
-- NOTE: make sure the cache directory @{cachePath} exists before calling this function or it will fail.
--
-- NOTE: this function _can_ fail but this is not expected to happen so the return value is mostly ignored for now.
--
@@ -659,7 +851,7 @@ end
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')
local file,msg = io.open(cachePath .. '/' .. M.STATE_FILE, 'w')
if not file then
E("error: could not open state file for writing (" .. msg .. ")")
@@ -682,7 +874,15 @@ end
-- so this file can also be used as a library.
-- Command-line arguments are expected to be present in the global `arg` variable.
local function main()
local argTable,msg = parseCommandlineArguments(arg)
-- NOTE: this require must be local to functions which are only executed on the wifibox (i.e., where we have uci)
package.path = package.path .. ';/usr/share/lua/wifibox/?.lua'
local settings = require('util.settings')
local defaults = { verbosity = 0, baseUrl = M.DEFAULT_BASE_URL, includeBetas = false, action = nil }
local confBaseUrl = settings.get('doodle3d.update.baseUrl')
if confBaseUrl and confBaseUrl:len() > 0 then defaults.baseUrl = confBaseUrl end
local argTable,msg = parseCommandlineArguments(arg, defaults)
if not argTable then
E("error interpreting command-line arguments, try '-h' for help (".. msg ..")")
@@ -690,7 +890,9 @@ local function main()
end
verbosity = argTable.verbosity
includeBetas = argTable.includeBetas
if argTable.useCache ~= nil then useCache = argTable.useCache end
if argTable.baseUrl ~= nil then baseUrl = argTable.baseUrl end
P(0, "Doodle3D Wifibox firmware updater")
local cacheCreated,msg = createCacheDirectory()
@@ -699,13 +901,16 @@ local function main()
os.exit(1)
end
P(0, (includeBetas and "Considering" or "Not considering") .. " beta releases.")
if argTable.action == 'showHelp' then
P(1, "\t-h\t\tShow this help message")
P(1, "\t-q\t\tquiet mode")
P(1, "\t-V\t\tverbose mode")
P(1, "\t-c\t\tUse cache as much as possible")
P(1, "\t-C\t\tDo not use the cache")
P(1, "\t-C\t\tDo not use the cache (default)")
P(1, "\t-u <base_url>\tUse specified base URL (default: " .. M.DEFAULT_BASE_URL .. ")")
P(1, "\t-b\t\tInclude beta releases")
P(1, "\t-v\t\tShow current image version")
P(1, "\t-s\t\tShow current update status")
P(1, "\t-l\t\tShow list of available image versions (and which one has been downloaded, if any)")
@@ -723,7 +928,7 @@ local function main()
P(1, "version: " .. M.formatVersion(v))
elseif argTable.action == 'showStatus' then
local status = M.getStatus()
local success,status,msg = M.getStatus(includeBetas)
P(0, "Current update status:")
P(1, " currentVersion:\t" .. (M.formatVersion(status.currentVersion) or '?'))
P(1, " newestVersion:\t" .. (M.formatVersion(status.newestVersion) or '?'))
@@ -740,7 +945,7 @@ local function main()
end
elseif argTable.action == 'showAvailableVersions' then
local verTable,msg = M.getAvailableVersions()
local verTable,msg = M.getAvailableVersions(includeBetas and 'both' or 'stables')
if not verTable then
E("error collecting version information (" .. msg .. ")")
os.exit(2)
@@ -750,7 +955,7 @@ local function main()
for _,ent in ipairs(verTable) do P(1, M.formatVersion(ent.version)) end
elseif argTable.action == 'showVersionInfo' then
local vEnt,msg = M.findVersion(argTable.version)
local vEnt,msg = M.findVersion(argTable.version, includeBetas)
if vEnt then
P(0, "Information on version:")
@@ -761,6 +966,7 @@ local function main()
P(1, " factoryFilename:\t" .. (vEnt.factoryFilename or '-'))
P(1, " factoryFileSize:\t" .. (vEnt.factoryFileSize or '-'))
P(1, " factoryMD5:\t\t" .. (vEnt.factoryMD5 or '-'))
P(1, " releaseDate:\t\t" .. (vEnt.timestamp and M.formatDate(vEnt.timestamp) or '-'))
if vEnt.changelog then
P(1, "\n--- Changelog ---\n" .. vEnt.changelog .. '---')
else
@@ -775,7 +981,7 @@ local function main()
end
elseif argTable.action == 'imageDownload' then
local vEnt,msg = M.findVersion(argTable.version)
local vEnt,msg = M.findVersion(argTable.version, includeBetas)
if vEnt == false then
P(1, "no such version")
os.exit(4)
@@ -797,7 +1003,7 @@ local function main()
elseif argTable.action == 'imageInstall' then
local vEnt, msg = nil, nil
vEnt,msg = M.findVersion(argTable.version)
vEnt,msg = M.findVersion(argTable.version, includeBetas)
if vEnt == false then
P(1, "no such version")
os.exit(4)
+2 -1
View File
@@ -6,7 +6,8 @@
# See file LICENSE.txt or visit http://www.gnu.org/licenses/gpl.html for full license details.
# start after networking
START=22
#START=22
START=61
LOGGER="logger -t autowifi -p 6"
+23 -1
View File
@@ -8,12 +8,34 @@
local log = require('util.logger')
local utils = require('util.utils')
local printerUtils = require('util.printer')
local M = {}
function M.hasControl(ip)
local controllerIP = M.getController()
return (controllerIP == "" or (controllerIP ~= "" and controllerIP == ip))
-- no controller stored? we have control
if controllerIP == "" then return true end;
-- controller stored is same as our (requesting) ip? we have control
if(controllerIP == ip) then return true end;
-- no printer connected? we have control
local printer,msg = printerUtils.createPrinterOrFail()
if not printer or not printer:hasSocket() then
M.setController("") -- clear the controller
return true
end
-- printer is idle (done printing)? we have control
local state = printer:getState()
if state == "idle" then -- TODO: define in constants somewhere
M.setController("") -- clear controller
return true
end
return false
end
function M.getController()
+4 -2
View File
@@ -78,8 +78,10 @@ function M.createPrinterOrFail(deviceId, response)
end
if not printer then
response:setError("could not open printer driver (" .. msg .. ")")
response:addData('id', deviceId)
if response ~= nil then
response:setError("could not open printer driver (" .. msg .. ")")
response:addData('id', deviceId)
end
return nil
end