mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-30 14:43:47 +01:00
Add LDoc configuration and slightly extend doxify.sh.
Add documentation in various places.
This commit is contained in:
parent
beb7f39471
commit
97564de8fc
16
config.ld
Normal file
16
config.ld
Normal file
@ -0,0 +1,16 @@
|
||||
file = {
|
||||
'src',
|
||||
exclude = {'src/test'}
|
||||
}
|
||||
|
||||
dir = 'docs'
|
||||
format = 'markdown'
|
||||
|
||||
readme = 'README.md'
|
||||
title = "WiFibox firmware documentation"
|
||||
project = "Wifibox firmware"
|
||||
description = "This project provides the REST API which is part of the Doodle3D wifibox."
|
||||
|
||||
alias('p', 'param')
|
||||
|
||||
all = true
|
11
doxify.sh
11
doxify.sh
@ -10,7 +10,16 @@ HTML_PATH=$WIFIBOX_BASE_DIR/docs
|
||||
SRC_DIR=$WIFIBOX_BASE_DIR/src
|
||||
FILESPEC=$WIFIBOX_BASE_DIR/src #replace by config.ld so we can also specify README.md?
|
||||
|
||||
$LDOC -d $HTML_PATH $FILESPEC -a -f markdown $@
|
||||
LUA_VERSION=`lua -v 2>&1 | awk -F" " '{print $2}'`
|
||||
|
||||
echo $LUA_VERSION | grep -q "^5.2"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Lua 5.2 is needed to run this script (as well as luarocks), you have $LUA_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#$LDOC -d $HTML_PATH $FILESPEC -a -f markdown $@
|
||||
$LDOC .
|
||||
|
||||
if [ $? -eq 127 ]; then
|
||||
echo "$0: It looks like the ldoc program could not be found, please configure the LDOC variable correctly and make sure ldoc is installed on your system."
|
||||
|
@ -1,34 +1,40 @@
|
||||
--[[--
|
||||
TODO: finish documentation
|
||||
This file contains all valid configuration keys, their default values and optional constraints.
|
||||
The table names are used as configuration key names, where underscores ('_') may be used to denote semi-categories.
|
||||
The settings interface replaces periods ('.') by underscores so for instance 'network.ap.address' will
|
||||
be translated to 'network_ap_address'. Multi-word names should be notated as camelCase.
|
||||
Valid fields for the tables are:
|
||||
- default: the default value (used when the key is not set in UCI config)
|
||||
- type: used for basic type checking, one of bool, int, float or string
|
||||
- description: A descriptive text usable by API clients
|
||||
- min, max, regex: optional constraints (min and max constrain value for numbers, or length for strings)
|
||||
|
||||
NOTE that the all-caps definitions will be changed into configuration keys, or moved to a different location
|
||||
]]--
|
||||
---
|
||||
-- This file contains all valid configuration keys, their default values and optional constraints.
|
||||
-- The table names are used as configuration key names, where underscores ('`_`') may be used to denote semi-categories.
|
||||
-- The settings interface replaces periods ('`.`') by underscores so for instance `network.ap.address` will
|
||||
-- be translated to `network_ap_address`. Multi-word names should be notated as camelCase.
|
||||
--
|
||||
-- Valid fields for the tables are:
|
||||
--
|
||||
-- - _default_: the default value (used when the key is not set in UCI config)
|
||||
-- - _type_: used for basic type checking, one of bool, int, float or string
|
||||
-- - _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
|
||||
--
|
||||
-- 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.
|
||||
local printer = require('util.printer')
|
||||
local log = require('util.logger')
|
||||
local utils = require('util.utils')
|
||||
|
||||
local M = {}
|
||||
|
||||
--NOTE: pcall protects from invocation exceptions, which is what we need except
|
||||
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
|
||||
--- This constant should only be true during development. It replaces `pcall` by regular `call`.
|
||||
-- Pcall protects the script from invocation exceptions, which is what we need except during debugging.
|
||||
-- When this flag is true, normal calls will be used so we can inspect stack traces.
|
||||
M.DEBUG_PCALLS = false
|
||||
|
||||
--This enables debugging of the REST API from the command-line, specify the path and optionally the request method as follows: 'p=/mod/func rq=POST'
|
||||
--- This constant enables debugging of the REST API from the command-line by emulating GET/POST requests.
|
||||
-- Specify the path and optionally the request method as follows: `d3dapi p=/mod/func r=POST`.
|
||||
M.DEBUG_API = true
|
||||
|
||||
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||
--- If enabled, REST responses will contain 'module' and 'function' keys describing what was requested.
|
||||
M.API_INCLUDE_ENDPOINT_INFO = false
|
||||
|
||||
M.API_BASE_URL_PATH = 'doodle3d.com' -- includes any base path if necessary (e.g. 'localhost/~user')
|
||||
--- This base path is used in @{rest.response}. It includes any base path if necessary (e.g. 'localhost/~user').
|
||||
M.API_BASE_URL_PATH = 'doodle3d.com'
|
||||
|
||||
M.network_ap_ssid = {
|
||||
default = 'Doodle3D-%%MAC_ADDR_TAIL%%',
|
||||
@ -50,7 +56,7 @@ M.network_ap_key = {
|
||||
type = 'string',
|
||||
description = 'Access Point security key',
|
||||
isValid = function(value)
|
||||
if value == "" then
|
||||
if value == "" then
|
||||
return true;
|
||||
elseif value:len() < 8 then
|
||||
return false, "too short"
|
||||
|
@ -1,3 +1,5 @@
|
||||
---
|
||||
-- This object represents an HTTP request object, part of the REST API.
|
||||
local util = require('util.utils') -- required for string:split()
|
||||
local urlcode = require('util.urlcode')
|
||||
local confDefaults = require('conf_defaults')
|
||||
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
-- The REST response object handles all operations necessary to generate a HTTP response.
|
||||
-- It knows about the request, ensures the correct HTTP headers are present and
|
||||
-- allows to set a status (one of `success`, `fail` or `error`) and addtional data.
|
||||
local JSON = require('util/JSON')
|
||||
local settings = require('util.settings')
|
||||
local defaults = require('conf_defaults')
|
||||
@ -10,6 +14,9 @@ local REQUEST_ID_ARGUMENT = 'rq_id'
|
||||
M.httpStatusCode, M.httpStatusText, M.contentType = nil, nil, nil
|
||||
M.binaryData, M.binarySavename = nil, nil
|
||||
|
||||
--- Print a HTTP header line with the given type and value.
|
||||
-- @string headerType
|
||||
-- @string headerValue
|
||||
local function printHeaderLine(headerType, headerValue)
|
||||
io.write(headerType .. ": " .. headerValue .. "\r\n")
|
||||
end
|
||||
@ -21,7 +28,10 @@ setmetatable(M, {
|
||||
end
|
||||
})
|
||||
|
||||
--requestObject should always be passed (except on init failure, when it is not yet available)
|
||||
--- Instantiates a new response object initialized with the given @{request} object.
|
||||
-- The request object should always be passed, except on init failure, when it is not yet available.
|
||||
-- @tparam request requestObject The object representing the HTTP request.
|
||||
-- @treturn response A newly instantiated response object.
|
||||
function M.new(requestObject)
|
||||
local self = setmetatable({}, M)
|
||||
self.body = { status = nil, data = {} }
|
||||
@ -29,9 +39,9 @@ function M.new(requestObject)
|
||||
self:setContentType('text/plain;charset=UTF-8')
|
||||
--self:setContentType('application/json;charset=UTF-8')
|
||||
|
||||
-- a queue for functions to be executed when the response has bin given
|
||||
-- needed for api calls like network/associate, which requires a restart of the webserver
|
||||
self.postResponseQueue = {}
|
||||
-- A queue for functions to be executed when the response has been given.
|
||||
-- Needed for api calls like network/associate, which requires a restart of the webserver.
|
||||
self.postResponseQueue = {}
|
||||
|
||||
if requestObject ~= nil then
|
||||
local rqId = requestObject:get(REQUEST_ID_ARGUMENT)
|
||||
@ -46,25 +56,37 @@ function M.new(requestObject)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set HTTP status (the default is `200 OK`).
|
||||
-- @number code The status code.
|
||||
-- @string text The status text, this should of course correspond to the given code.
|
||||
function M:setHttpStatus(code, text)
|
||||
if code ~= nil then self.httpStatusCode = code end
|
||||
if text ~= nil then self.httpStatusText = text end
|
||||
end
|
||||
|
||||
--- Set the HTTP content type (the default is `text/plain;charset=UTF-8`).
|
||||
-- @string contentType The content type to set.
|
||||
function M:setContentType(contentType)
|
||||
if contentType ~= nil then self.contentType = contentType end
|
||||
end
|
||||
|
||||
--- Set REST status to success.
|
||||
-- @string[opt] msg An optional human-readable message to include with the status.
|
||||
function M:setSuccess(msg)
|
||||
self.body.status = 'success'
|
||||
if msg ~= '' then self.body.msg = msg end
|
||||
end
|
||||
|
||||
--- Set REST status to failure.
|
||||
-- @string[opt] msg An optional human-readable message to include with the status.
|
||||
function M:setFail(msg)
|
||||
self.body.status = 'fail'
|
||||
if msg ~= '' then self.body.msg = msg end
|
||||
end
|
||||
|
||||
--- Set REST status to error.
|
||||
-- A reference to the API documentation will also be included.
|
||||
-- @string[opt] msg An optional human-readable message to include with the status.
|
||||
function M:setError(msg)
|
||||
self.body.status = 'error'
|
||||
if msg ~= '' then self.body.msg = msg end
|
||||
@ -72,17 +94,31 @@ function M:setError(msg)
|
||||
self:addData('more_info', 'http://' .. defaults.API_BASE_URL_PATH .. '/wiki/wiki/communication-api')
|
||||
end
|
||||
|
||||
--NOTE: with this method, to add nested data, it is necessary to precreate the table and add it with its root key
|
||||
--(e.g.: response:addData('data', {f1=3, f2='x'}))
|
||||
--- Adds a data item to the response, this will be included under the `data` item of the json text.
|
||||
--
|
||||
-- NOTE: To add nested data with this method, it is necessary to precreate the table
|
||||
-- and then add that with its root key. (e.g., `response:addData('f_values', {f1=3, f2='x'})`)
|
||||
--
|
||||
-- After calling this, any binary data set by @{M:setBinaryFileData} will not be sent anymore.
|
||||
--
|
||||
-- @string k The key of the item to set.
|
||||
-- @param v The value to set.
|
||||
function M:addData(k, v)
|
||||
self.body.data[k] = v
|
||||
self.binaryData = nil
|
||||
end
|
||||
|
||||
--- Queue a function for execution after the response has been passed back to the webserver.
|
||||
--
|
||||
-- Note that this is not useful in many cases since the webserver will not actually send
|
||||
-- the response until this script finishes. So for instance if the queue contains code
|
||||
-- to restart the webserver, the response will never be sent out.
|
||||
-- @func fn The function to queue.
|
||||
function M:addPostResponseFunction(fn)
|
||||
table.insert(self.postResponseQueue, fn)
|
||||
end
|
||||
|
||||
--- Call all function on the post-response queue, see @{M:addPostResponseFunction} for details and a side-note.
|
||||
function M:executePostResponseQueue()
|
||||
--local utils = require('util.utils')
|
||||
--local log = require('util.logger')
|
||||
@ -91,16 +127,23 @@ function M:executePostResponseQueue()
|
||||
for i,fn in ipairs(self.postResponseQueue) do fn() end
|
||||
end
|
||||
|
||||
--- Returns an API url pointing to @{conf_defaults.API_BASE_URL_PATH}, which is quite useless.
|
||||
-- @string mod
|
||||
-- @string func
|
||||
-- @treturn string A not-so-useful URL.
|
||||
function M:apiURL(mod, func)
|
||||
if not mod then return nil end
|
||||
if func then func = '/' .. func else func = "" end
|
||||
return 'http://' .. defaults.API_BASE_URL_PATH .. '/cgi-bin/d3dapi/' .. mod .. func
|
||||
end
|
||||
|
||||
--- Returns the body data contained in this object as [JSON](http://www.json.org/).
|
||||
-- @treturn string The JSON data.
|
||||
function M:serializeAsJson()
|
||||
return JSON:encode(self.body)
|
||||
end
|
||||
|
||||
--- Writes HTTP headers, followed by an HTTP body containing JSON data to stdout.
|
||||
function M:send()
|
||||
printHeaderLine("Status", self.httpStatusCode .. " " .. self.httpStatusText)
|
||||
printHeaderLine("Content-Type", self.contentType)
|
||||
@ -116,6 +159,14 @@ function M:send()
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the response object to return binary data instead of JSON as its body.
|
||||
--
|
||||
-- After calling this, REST data and status will not be sent anymore.
|
||||
-- @string rFile The file on the local file system to read the data from.
|
||||
-- @string saveName The file name to suggest the user to save the data in.
|
||||
-- @string contentType The content type of the data.
|
||||
-- @treturn bool|nil True, or nil in which case the second argument will be set.
|
||||
-- @treturn ?string An error message if the first argument is nil.
|
||||
function M:setBinaryFileData(rFile, saveName, contentType)
|
||||
if type(rFile) ~= 'string' or rFile:len() == 0 then return false end
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
--- 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:
|
||||
-- add to status: validImage: none|<version> (can use checkValidImage for this)
|
||||
-- any more TODO's across this file?
|
||||
@ -7,9 +10,8 @@
|
||||
|
||||
-- MAYBE/LATER:
|
||||
-- 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
|
||||
-- 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:)
|
||||
-- remove /etc/wifibox-version on macbook...
|
||||
-- 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)
|
||||
-- note: take care not to print any text in module functions, as this breaks http responses
|
||||
@ -17,18 +19,39 @@
|
||||
|
||||
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, DOWNLOAD_FAILED = 3, IMAGE_READY = 4, INSTALLING = 5, INSTALLED = 6, INSTALL_FAILED = 7 }
|
||||
--- Possible states the updater can be in, they are stored in @{STATE_FILE}.
|
||||
-- @table STATE
|
||||
M.STATE = {
|
||||
NONE = 1, -- @{STATE_FILE} does not exist
|
||||
DOWNLOADING = 2, -- downloading is started but not finished yet
|
||||
DOWNLOAD_FAILED = 3, -- download failed (often occurs when the wifibox is not connected to internet)
|
||||
IMAGE_READY = 4, -- download succeeded and the image is valid
|
||||
INSTALLING = 5, -- image is being installed (this state will probably never be returned since the box is flashing/rebooting)
|
||||
INSTALLED = 6, -- image has been installed successfully (this state will never be returned since the box will reboot)
|
||||
INSTALL_FAILED = 7 -- installation failed
|
||||
}
|
||||
|
||||
-- Names for the states in @{STATE}, these are returned through the REST API.
|
||||
M.STATE_NAMES = {
|
||||
[M.STATE.NONE] = 'none', [M.STATE.DOWNLOADING] = 'downloading', [M.STATE.DOWNLOAD_FAILED] = 'download_failed', [M.STATE.IMAGE_READY] = 'image_ready',
|
||||
[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.
|
||||
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'
|
||||
|
||||
--- Path to the updater cache.
|
||||
M.CACHE_PATH = '/tmp/d3d-updater'
|
||||
|
||||
--- Name of the file to store current state in, this file resides in @{CACHE_PATH}.
|
||||
M.STATE_FILE = 'update-state'
|
||||
|
||||
M.WGET_OPTIONS = "-q -t 1 -T 30"
|
||||
--M.WGET_OPTIONS = "-v -t 1 -T 30"
|
||||
|
||||
@ -43,7 +66,10 @@ local baseUrl = M.DEFAULT_BASE_URL -- default, can be overwritten by M.setBaseUr
|
||||
-- LOCAL FUNCTIONS --
|
||||
---------------------
|
||||
|
||||
-- use level==1 for important messages, 0 for regular messages and -1 for less important messages
|
||||
--- Log a message with the given level, if logging is enabled for that level.
|
||||
-- Messages will be written to [stdout](http://www.cplusplus.com/reference/cstdio/stdout/), or logged using the logger set with @{setLogger}.
|
||||
-- @number lvl Level to log to, use 1 for important messages, 0 for regular messages and -1 for less important messages.
|
||||
-- @string msg The message to log.
|
||||
local function P(lvl, msg)
|
||||
if log then
|
||||
if lvl == -1 then log:debug(msg)
|
||||
@ -54,15 +80,28 @@ local function P(lvl, msg)
|
||||
end
|
||||
end
|
||||
|
||||
--- Log a debug message, this function wraps @{P}.
|
||||
-- The message will be logged with level -1 and be prefixed with '(DBG)'.
|
||||
-- @string msg The message to log.
|
||||
local function D(msg) P(-1, (log and msg or "(DBG) " .. msg)) end
|
||||
|
||||
--- Log an error.
|
||||
-- Messages will be written to [stderr](http://www.cplusplus.com/reference/cstdio/stderr/), or logged using the logger set with @{setLogger}.
|
||||
-- @string msg The message to log.
|
||||
local function E(msg)
|
||||
if log then log:error(msg)
|
||||
else io.stderr:write(msg .. '\n')
|
||||
end
|
||||
end
|
||||
|
||||
-- splits the return status from os.execute (see: http://stackoverflow.com/questions/16158436/how-to-shift-and-mask-bits-from-integer-in-lua)
|
||||
--- Splits the return status from `os.execute`, 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)).
|
||||
-- Furthermore, see [shifting bits in Lua](http://stackoverflow.com/questions/16158436/how-to-shift-and-mask-bits-from-integer-in-lua).
|
||||
-- @number exitStatus The combined exit status.
|
||||
-- @treturn number The command exit status.
|
||||
-- @treturn number The `os.execute`/[system](http://linux.die.net/man/3/system) return status.
|
||||
local function splitExitStatus(exitStatus)
|
||||
if exitStatus == -1 then return -1,-1 end
|
||||
local cmdStatus = math.floor(exitStatus / 256)
|
||||
@ -70,6 +109,9 @@ local function splitExitStatus(exitStatus)
|
||||
return cmdStatus, systemStatus
|
||||
end
|
||||
|
||||
--- Returns a human-readable message for a [wget exit status](http://www.gnu.org/software/wget/manual/wget.html#Exit-Status).
|
||||
-- @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)
|
||||
|
||||
@ -96,6 +138,9 @@ local function wgetStatusToString(exitStatus)
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates the updater cache directory.
|
||||
-- @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 .. "'"
|
||||
@ -103,6 +148,9 @@ local function createCacheDirectory()
|
||||
return true
|
||||
end
|
||||
|
||||
--- Retrieves the current updater state code and message from @{STATE_FILE}.
|
||||
-- @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')
|
||||
if not file then return M.STATE.NONE,"" end
|
||||
@ -113,13 +161,23 @@ local function getState()
|
||||
return code,msg
|
||||
end
|
||||
|
||||
-- trim whitespace from both ends of string (from http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76)
|
||||
--- Trims whitespace from both ends of a string.
|
||||
-- See [this Lua snippet](http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76).
|
||||
-- @string s The text to trim.
|
||||
-- @treturn string s, with whitespace trimmed.
|
||||
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
|
||||
--- Read the contents of a file.
|
||||
--
|
||||
-- TODO: this file has been copied from @{util.utils}.lua and should be merged again.
|
||||
-- @string filePath The file to read.
|
||||
-- @bool trimResult Whether or not to trim the read data.
|
||||
-- @treturn bool|nil True, or nil on error.
|
||||
-- @treturn ?string A descriptive message on error.
|
||||
-- @treturn ?number TODO: find out why this value is returned.
|
||||
local function readFile(filePath, trimResult)
|
||||
local f, msg, nr = io.open(filePath, 'r')
|
||||
if not f then return nil,msg,nr end
|
||||
@ -134,7 +192,11 @@ local function readFile(filePath, trimResult)
|
||||
return res
|
||||
end
|
||||
|
||||
-- from utils.lua
|
||||
--- Reports whether or not a file exists.
|
||||
--
|
||||
-- TODO: this file has been copied from @{util.utils}.lua and should be merged again.
|
||||
-- @string file The file to report about.
|
||||
-- @treturn bool True if the file exists, false otherwise.
|
||||
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"
|
||||
@ -145,8 +207,11 @@ local function exists(file)
|
||||
return r ~= nil
|
||||
end
|
||||
|
||||
-- from utils.lua
|
||||
--argument: either an open file or a filename
|
||||
--- Reports the size of a file or file handle.
|
||||
--
|
||||
-- TODO: this file has been copied from @{util.utils}.lua and should be merged again.
|
||||
-- @param file A file path or open file handle.
|
||||
-- @treturn number Size of the file.
|
||||
local function fileSize(file)
|
||||
local size = nil
|
||||
if type(file) == 'file' then
|
||||
@ -164,18 +229,27 @@ local function fileSize(file)
|
||||
return size
|
||||
end
|
||||
|
||||
-- returns return value of command
|
||||
--- Runs an arbitrary shell command.
|
||||
-- @string command The command to run.
|
||||
-- @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
|
||||
end
|
||||
|
||||
--- Removes a file.
|
||||
-- @string filePath The file to remove.
|
||||
local function removeFile(filePath)
|
||||
return runCommand('rm ' .. filePath)
|
||||
end
|
||||
|
||||
-- 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
|
||||
--- Downloads a file and stores it locally.
|
||||
-- @string url The full URL to download.
|
||||
-- @string saveDir The path at which to save the downloaded file.
|
||||
-- @string[opt] filename File name to save as, note that leaving this out has issues with files not being overwritten but suffixed with '.1', '.2',etc instead.
|
||||
-- @treturn number|nil Exit status of wget command or nil on error.
|
||||
-- @treturn ?string Descriptive message if saveDir is nil or empty.
|
||||
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 ''
|
||||
@ -186,6 +260,10 @@ local function downloadFile(url, saveDir, filename)
|
||||
end
|
||||
end
|
||||
|
||||
--- Parses command-line arguments and returns a table containing information distilled from them.
|
||||
-- @tab arglist A table in the same form as the [arg table](http://www.lua.org/pil/1.4.html) created by Lua.
|
||||
-- @treturn tabla|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 nextIsVersion, nextIsUrl = false, false
|
||||
@ -233,18 +311,35 @@ end
|
||||
-- MODULE FUNCTIONS --
|
||||
----------------------
|
||||
|
||||
--- 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)
|
||||
log = logger
|
||||
end
|
||||
|
||||
--- Controls whether or not to use pre-existing files over (re-)downloading them.
|
||||
--
|
||||
-- Note that the mechanism is currently naive, (e.g., there are no mechanisms like maximum cache age).
|
||||
-- @bool use If true, try not to download anything unless necessary.
|
||||
function M.setUseCache(use)
|
||||
useCache = use
|
||||
end
|
||||
|
||||
--- Sets the base URL to use for finding update images, defaults to @{DEFAULT_BASE_URL}.
|
||||
-- @string url The new base URL to use.
|
||||
function M.setBaseUrl(url)
|
||||
baseUrl = url
|
||||
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.
|
||||
--
|
||||
-- @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()
|
||||
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
|
||||
local unknownVersion = { major = 0, minor = 0, patch = 0 }
|
||||
@ -253,7 +348,7 @@ function M.getStatus()
|
||||
result.currentVersion = M.getCurrentVersion()
|
||||
result.stateCode, result.stateText = getState()
|
||||
result.stateCode = tonumber(result.stateCode)
|
||||
|
||||
|
||||
local verTable,msg = M.getAvailableVersions()
|
||||
if not verTable then
|
||||
D("could not obtain available versions (" .. msg .. ")")
|
||||
@ -263,7 +358,7 @@ function M.getStatus()
|
||||
|
||||
local newest = verTable and verTable[#verTable]
|
||||
result.newestVersion = newest and newest.version or unknownVersion
|
||||
|
||||
|
||||
if result.stateCode == M.STATE.DOWNLOADING then
|
||||
result.progress = fileSize(M.CACHE_PATH .. '/' .. newest.sysupgradeFilename)
|
||||
if not result.progress then result.progress = 0 end -- in case the file does not exist yet (which yields nil)
|
||||
@ -525,6 +620,10 @@ end
|
||||
-- MAIN --
|
||||
----------
|
||||
|
||||
--- The main function which will be called in standalone mode.
|
||||
-- At the end of the file, this function will be invoked only if `arg` is defined,
|
||||
-- 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)
|
||||
|
||||
@ -662,7 +761,7 @@ local function main()
|
||||
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
|
||||
--- 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
|
||||
|
@ -1,8 +1,5 @@
|
||||
--[[--
|
||||
TODO: ldoc: @{} ref in init() tformat
|
||||
TODO: use macros/type definitions to document rest modules (to auto-match things like 'M.<func>_NAME%')?
|
||||
TODO: finish documentation
|
||||
]]
|
||||
---
|
||||
-- Logging facilities.
|
||||
|
||||
local utils = require('util.utils')
|
||||
|
||||
@ -10,7 +7,8 @@ local M = {}
|
||||
|
||||
local logLevel, logVerbose, logStream
|
||||
|
||||
--- Available log levels.
|
||||
--- Available log levels
|
||||
-- @table LEVEL
|
||||
M.LEVEL = {
|
||||
'debug', -- for debug messages
|
||||
'info', -- for informational messages
|
||||
@ -34,10 +32,10 @@ local function log(level, msg, verbose)
|
||||
local name = i.name or "(nil)"
|
||||
local vVal = 'nil'
|
||||
local m = (type(msg) == 'string') and msg or utils.dump(msg)
|
||||
|
||||
|
||||
if v then logStream:write(now .. " (" .. M.LEVEL[level] .. ") " .. m .. " [" .. name .. "@" .. i.short_src .. ":" .. i.linedefined .. "]\n")
|
||||
else logStream:write(now .. " (" .. M.LEVEL[level] .. ") " .. m .. "\n") end
|
||||
|
||||
|
||||
logStream:flush()
|
||||
end
|
||||
end
|
||||
|
@ -1,15 +1,9 @@
|
||||
--[[--
|
||||
The settings interface reads and writes configuration keys using UCI.
|
||||
All keys have pre-defined defaults in @{conf_defaults} which will be used
|
||||
if no value is stored in the UCI config. The UCI config file is
|
||||
'/etc/config/wifibox'.
|
||||
The default values guarantee there will always be a set of reasonable settings
|
||||
to use and provide a clear overview of all existing configuration keys as well.
|
||||
|
||||
By the way, returning correct values in get()/fromUciValue() for booleans has
|
||||
been fixed at a relatively convenient time purely thanks to the unit tests...
|
||||
just to indicate how useful they are. :)
|
||||
]]
|
||||
---
|
||||
-- The settings interface reads and writes configuration keys using [UCI](http://wiki.openwrt.org/doc/uci).
|
||||
-- All keys have pre-defined defaults in @{conf_defaults} which will be used
|
||||
-- if no value is stored in the UCI config. The UCI config file is `/etc/config/wifibox`.
|
||||
-- The default values guarantee there will always be a set of reasonable settings
|
||||
-- to use and provide a clear overview of all existing configuration keys as well.
|
||||
local uci = require('uci').cursor()
|
||||
local utils = require('util.utils')
|
||||
local baseconfig = require('conf_defaults')
|
||||
@ -18,26 +12,26 @@ local log = require('util.logger')
|
||||
|
||||
local M = {}
|
||||
|
||||
--- UCI config name (i.e. file under /etc/config)
|
||||
--- UCI config name (i.e., file under `/etc/config`)
|
||||
local UCI_CONFIG_NAME = 'wifibox'
|
||||
|
||||
--- Absolute path to the UCI config file
|
||||
local UCI_CONFIG_FILE = '/etc/config/' .. UCI_CONFIG_NAME
|
||||
|
||||
--- Section type that will be used in UCI\_CONFIG\_FILE
|
||||
--- [Section type](http://wiki.openwrt.org/doc/techref/uci#about.uci.structure) that will be used in @{UCI_CONFIG_FILE}
|
||||
local UCI_CONFIG_TYPE = 'settings'
|
||||
|
||||
--- Section name that will be used for 'public' settings (as predefined in conf_defaults.lua) in UCI\_CONFIG\_FILE
|
||||
--- [Section name](http://wiki.openwrt.org/doc/techref/uci#about.uci.structure) that will be used for 'public' settings (as predefined in conf_defaults.lua) in @{UCI_CONFIG_FILE}
|
||||
local UCI_CONFIG_SECTION = 'general'
|
||||
|
||||
--- Section name that will be used for 'firmware-local' settings in UCI\_CONFIG\_FILE
|
||||
--- [Section name](http://wiki.openwrt.org/doc/techref/uci#about.uci.structure) that will be used for 'firmware-local' settings in @{UCI_CONFIG_FILE}
|
||||
local UCI_CONFIG_SYSTEM_SECTION = 'system'
|
||||
|
||||
local ERR_NO_SUCH_KEY = "key does not exist"
|
||||
|
||||
|
||||
--- Returns a key with all periods ('.') replaced by underscores ('_').
|
||||
-- @tparam string key The key for which to substitute dots.
|
||||
-- @string key The key for which to substitute dots.
|
||||
-- @return The substituted key, or the key parameter itself if it is not of type 'string'.
|
||||
local function replaceDots(key)
|
||||
if type(key) ~= 'string' then return key end
|
||||
@ -46,7 +40,7 @@ local function replaceDots(key)
|
||||
end
|
||||
|
||||
--- Returns a key with all underscores ('_') replaced by periods ('.').
|
||||
-- @tparam string key The key for which to substitute underscores.
|
||||
-- @string key The key for which to substitute underscores.
|
||||
-- @return The substituted key, or the key parameter itself if it is not of type 'string'.
|
||||
local function replaceUnderscores(key)
|
||||
if type(key) ~= 'string' then return key end
|
||||
@ -57,13 +51,13 @@ end
|
||||
--- Converts a lua value to equivalent representation for UCI.
|
||||
-- Boolean values are converted to '1' and '0', everything else is converted to a string.
|
||||
--
|
||||
-- @param v The value to convert.
|
||||
-- @param vType The type of the given value.
|
||||
-- @p v The value to convert.
|
||||
-- @p vType The type of the given value.
|
||||
-- @return A value usable to write to UCI.
|
||||
local function toUciValue(v, vType)
|
||||
if vType == 'bool' then return v and '1' or '0' end
|
||||
if(vType == 'string') then
|
||||
v = v:gsub('[\n\r]', '\\n')
|
||||
if(vType == 'string') then
|
||||
v = v:gsub('[\n\r]', '\\n')
|
||||
end
|
||||
|
||||
return tostring(v)
|
||||
@ -73,33 +67,33 @@ end
|
||||
-- For boolean, '1' is converted to true and everything else to false. Floats
|
||||
-- and ints are converted to numbers and everything else will be returned as is.
|
||||
--
|
||||
-- @param v The value to convert.
|
||||
-- @param vType The type of the given value.
|
||||
-- @p v The value to convert.
|
||||
-- @p vType The type of the given value.
|
||||
-- @return A lua value typed correctly with regard to the vType parameter.
|
||||
local function fromUciValue(v, vType)
|
||||
if v == nil then return nil end
|
||||
|
||||
|
||||
if vType == 'bool' then
|
||||
return (v == '1') and true or false
|
||||
elseif vType == 'float' or vType == 'int' then
|
||||
return tonumber(v)
|
||||
elseif vType == 'string' then
|
||||
v = v:gsub('\\n', '\n')
|
||||
v = v:gsub('\\n', '\n')
|
||||
return v
|
||||
else
|
||||
return v
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Reports whether a value is valid given the constraints specified in a base table.
|
||||
-- @param value The value to test.
|
||||
-- @tparam table baseTable The base table to use constraint data from (min,max,regex).
|
||||
-- @p value The value to test.
|
||||
-- @tab baseTable The base table to use constraint data from (min,max,regex).
|
||||
-- @treturn bool Returns true if the value is valid, false if it is not.
|
||||
local function isValid(value, baseTable)
|
||||
local varType, min, max, regex, isValid = baseTable.type, baseTable.min, baseTable.max, baseTable.regex, baseTable.isValid
|
||||
|
||||
if isValid then
|
||||
if isValid then
|
||||
local ok,msg = isValid(value)
|
||||
if msg == nil then msg = "invalid value" end
|
||||
return ok or nil,msg
|
||||
@ -107,7 +101,7 @@ local function isValid(value, baseTable)
|
||||
|
||||
if varType == 'bool' then
|
||||
return type(value) == 'boolean' or nil,"invalid bool value"
|
||||
|
||||
|
||||
elseif varType == 'int' or varType == 'float' then
|
||||
local numValue = tonumber(value)
|
||||
if numValue == nil then
|
||||
@ -119,7 +113,7 @@ local function isValid(value, baseTable)
|
||||
elseif max and numValue > max then
|
||||
return nil, "too high"
|
||||
end
|
||||
|
||||
|
||||
elseif varType == 'string' then
|
||||
local ok = true
|
||||
if min and value:len() < min then
|
||||
@ -128,14 +122,14 @@ local function isValid(value, baseTable)
|
||||
return nil,"too long"
|
||||
elseif regex and value:match(regex) == nil then
|
||||
return nil,"invalid value"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Looks up the table in conf_defaults.lua corresponding to a key.
|
||||
-- @tparam string key The key for which to return the base table.
|
||||
--- Looks up the table in @{conf_defaults}.lua corresponding to a key.
|
||||
-- @string key The key for which to return the base table.
|
||||
-- @treturn table The base table for key, or nil if it does not exist.
|
||||
local function getBaseKeyTable(key)
|
||||
local base = baseconfig[key]
|
||||
@ -144,20 +138,20 @@ end
|
||||
|
||||
|
||||
--- Returns the value of the requested key if it exists.
|
||||
-- @param key The key to return the associated value for.
|
||||
-- @p key The key to return the associated value for.
|
||||
-- @return The associated value, beware (!) that this may be boolean false for keys of 'bool' type.
|
||||
function M.get(key)
|
||||
key = replaceDots(key)
|
||||
local base = getBaseKeyTable(key)
|
||||
|
||||
|
||||
if not base then return nil,ERR_NO_SUCH_KEY end
|
||||
|
||||
|
||||
local v = base.default
|
||||
local uciV = fromUciValue(uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key), base.type)
|
||||
|
||||
local actualV = v
|
||||
if uciV ~= nil then actualV = uciV end
|
||||
|
||||
|
||||
return actualV
|
||||
end
|
||||
|
||||
@ -175,7 +169,7 @@ function M.getAll()
|
||||
end
|
||||
|
||||
--- Reports whether or not a key exists.
|
||||
-- @tparam string key The key to find.
|
||||
-- @string key The key to find.
|
||||
-- @treturn bool True if the key exists, false if not.
|
||||
function M.exists(key)
|
||||
key = replaceDots(key)
|
||||
@ -187,7 +181,7 @@ end
|
||||
-- if for instance, the default is 'abc', and UCI contains a configured value of
|
||||
-- 'abc' as well, that key is _not_ a default value.
|
||||
--
|
||||
-- @tparam string key The key to report about.
|
||||
-- @string key The key to report about.
|
||||
-- @treturn bool True if the key is currently at its default value, false if not.
|
||||
function M.isDefault(key)
|
||||
key = replaceDots(key)
|
||||
@ -196,8 +190,8 @@ function M.isDefault(key)
|
||||
end
|
||||
|
||||
--- Sets a key to a new value or reverts it to the default value.
|
||||
-- @tparam string key The key to set.
|
||||
-- @param value The value or set, or nil to revert key to its default value.
|
||||
-- @string key The key to set.
|
||||
-- @p[opt=nil] value The value or set, or nil to revert key to its default value.
|
||||
-- @treturn bool|nil True if everything went well, nil in case of error.
|
||||
-- @treturn ?string Error message in case first return value is nil (invalid key).
|
||||
function M.set(key, value)
|
||||
@ -206,10 +200,10 @@ function M.set(key, value)
|
||||
|
||||
local r = utils.create(UCI_CONFIG_FILE)
|
||||
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, UCI_CONFIG_TYPE)
|
||||
|
||||
|
||||
local base = getBaseKeyTable(key)
|
||||
if not base then return nil,ERR_NO_SUCH_KEY end
|
||||
|
||||
|
||||
if M.isDefault(key) and value == nil then return true end -- key is default already
|
||||
--log:info(" not default")
|
||||
local current = uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)
|
||||
@ -222,7 +216,7 @@ function M.set(key, value)
|
||||
end
|
||||
elseif base.type == 'int' or base.type == 'float' then
|
||||
value = tonumber(value)
|
||||
if(value == nil) then
|
||||
if(value == nil) then
|
||||
return nil,"Value isn't a valid int or float"
|
||||
end
|
||||
end
|
||||
@ -232,7 +226,7 @@ function M.set(key, value)
|
||||
if not valid then
|
||||
return nil,m
|
||||
end
|
||||
|
||||
|
||||
if fromUciValue(current, base.type) == value then return true end
|
||||
|
||||
if value ~= nil then
|
||||
@ -240,13 +234,13 @@ function M.set(key, value)
|
||||
else
|
||||
uci:delete(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)
|
||||
end
|
||||
|
||||
|
||||
uci:commit(UCI_CONFIG_NAME)
|
||||
return true
|
||||
end
|
||||
|
||||
--- Returns a UCI configuration key from the system section.
|
||||
-- @tparam string key The key for which to return the value, must be non-empty.
|
||||
-- @string key The key for which to return the value, must be non-empty.
|
||||
-- @return Requested value or false if it does not exist or nil on invalid key.
|
||||
function M.getSystemKey(key)
|
||||
if type(key) ~= 'string' or key:len() == 0 then return nil end
|
||||
@ -254,21 +248,21 @@ function M.getSystemKey(key)
|
||||
return v or false
|
||||
end
|
||||
|
||||
--- Sets the given key to the given value.
|
||||
--- Sets the value of a UCI key in the system section.
|
||||
-- Note that unlike the public settings, system keys are untyped and value must
|
||||
-- be of type string; UCI generally uses '1' and '0' for boolean values.
|
||||
-- @tparam string key The key to set, must be non-empty.
|
||||
-- @tparam string value The value to set key to.
|
||||
-- @return True on success or false if key or value arguments are invalid.
|
||||
-- @string key The key to set, must be non-empty.
|
||||
-- @string value The value to set key to.
|
||||
-- @return True on success or nil if key or value arguments are invalid.
|
||||
function M.setSystemKey(key, value)
|
||||
if type(key) ~= 'string' or key:len() == 0 then return nil end
|
||||
if type(value) ~= 'string' then return nil end
|
||||
|
||||
|
||||
local r = utils.create(UCI_CONFIG_FILE) -- make sure the file exists for uci to write to
|
||||
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SYSTEM_SECTION, UCI_CONFIG_TYPE)
|
||||
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SYSTEM_SECTION, key, value)
|
||||
uci:commit(UCI_CONFIG_NAME)
|
||||
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
--[[--
|
||||
TODO: finish documentation
|
||||
The unavoidable collection of utility functions.
|
||||
]]
|
||||
---
|
||||
-- The unavoidable collection of utility functions.
|
||||
-- TODO: use macros/type definitions to document rest modules (to auto-match things like 'M.<func>_NAME%')?
|
||||
|
||||
local M = {}
|
||||
|
||||
@ -25,7 +24,7 @@ end
|
||||
|
||||
function M.toboolean(s)
|
||||
if not s then return false end
|
||||
|
||||
|
||||
local b = type(s) == 'string' and s:lower() or s
|
||||
local textTrue = (b == '1' or b == 't' or b == 'true')
|
||||
local boolTrue = (type(b) == 'boolean' and b == true)
|
||||
@ -63,7 +62,7 @@ function M.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
|
||||
@ -72,16 +71,16 @@ end
|
||||
--creates and returns true if not exists, returns false it does, nil+msg on error
|
||||
function M.create(file)
|
||||
local r,m = M.exists(file)
|
||||
|
||||
|
||||
if r == nil then
|
||||
return r,m
|
||||
elseif r == true then
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
r,m = io.open(file, 'a') -- append mode is probably safer in case the file does exist after all
|
||||
if not r then return r,m end
|
||||
|
||||
|
||||
r:close()
|
||||
return true
|
||||
end
|
||||
@ -96,10 +95,10 @@ end
|
||||
function M.readFile(filePath)
|
||||
local f, msg, nr = io.open(filePath, 'r')
|
||||
if not f then return nil,msg,nr end
|
||||
|
||||
|
||||
local res = f:read('*all')
|
||||
f:close()
|
||||
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user