mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 11:03:48 +01:00
Merge branch 'develop'
This commit is contained in:
commit
349210a3fc
@ -1,3 +1,15 @@
|
||||
Changelog
|
||||
# 0.10.2 (14th mar 2014)
|
||||
- Fixed connection issues to networks with multiple routers sharing the same ssid
|
||||
- Option to update to beta releases (Beta testers are welcome)
|
||||
- Substituted wifiboxid retrievable through info api
|
||||
- Added a "connecting" printer state where it found a printer but couldn't communicate yet. (Not yet implemented for Makerbot's)
|
||||
- When connecting takes to long, we display a warning that the user might have selected the wrong printer type (Not yet implemented for Makerbot's)
|
||||
- API's printer/state doesn't return an error anymore when a printer is just connected
|
||||
- Allowing WiFi channels 12 & 13
|
||||
- Fixed issue that control access wasn't properly reset after print
|
||||
- Fixed another issue where the box in access point wouldn't give ip addresses
|
||||
|
||||
# 0.10.1 (12th feb 2014)
|
||||
- miniFactory support
|
||||
- Fixed most Makerbot display issues
|
||||
|
@ -1,2 +0,0 @@
|
||||
alias d='ls -la --color=auto'
|
||||
alias wopkg='/usr/share/lua/wifibox/opkg.conf'
|
@ -1,188 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# NOTE: this script generates an index based on images found in the target directory.
|
||||
# So make sure it contains all images ever released (unless you want to actually remove them).
|
||||
# If this is not the case, first create a mirror of doodle3d.com/updates/images.
|
||||
|
||||
#prevent being run as root (which is dangerous)
|
||||
if [ "$(id -u)" == "0" ]; then
|
||||
echo "Don't run this script as root, it is potentially dangerous." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# expects path of file to return the size of
|
||||
fileSize() {
|
||||
stat -f %z ${1}
|
||||
}
|
||||
|
||||
# expects arguments: version, devType, sysupgrade|factory
|
||||
# image name will be: '${IMAGE_BASENAME}-<version>-<devType>-<sysupgrade|factory>.bin'
|
||||
constructImageName() {
|
||||
if [ $# -lt 3 ]; then echo "incorrect usage of constructImageName()"; exit 1; fi
|
||||
echo "${IMAGE_BASENAME}-${1}-${2}-${3}.bin"
|
||||
}
|
||||
|
||||
# expects arguments: basePath (where the files are), version, devType
|
||||
generateIndexEntry() {
|
||||
if [ $# -lt 3 ]; then echo "incorrect usage of generateIndexEntry()"; exit 1; fi
|
||||
|
||||
sysupgrade_out_basename=`constructImageName ${2} ${3} sysupgrade`
|
||||
factory_out_basename=`constructImageName ${2} ${3} factory`
|
||||
sysupgrade_out_file=${1}/${sysupgrade_out_basename}
|
||||
factory_out_file=${1}/${factory_out_basename}
|
||||
|
||||
sysupgrade_filesize=`fileSize ${sysupgrade_out_file}`
|
||||
factory_filesize=`fileSize ${factory_out_file}`
|
||||
sysupgrade_md5sum=`md5 -q ${sysupgrade_out_file}`
|
||||
factory_md5sum=`md5 -q ${factory_out_file}`
|
||||
echo "Version: ${2}" >> $IMG_INDEX_FILE
|
||||
echo "Files: ${sysupgrade_out_basename}; ${factory_out_basename}" >> $IMG_INDEX_FILE
|
||||
echo "FileSize: ${sysupgrade_filesize}; ${factory_filesize}" >> $IMG_INDEX_FILE
|
||||
echo "MD5: ${sysupgrade_md5sum}; ${factory_md5sum}" >> $IMG_INDEX_FILE
|
||||
}
|
||||
|
||||
|
||||
OPENWRT_BASE=.
|
||||
PKG_SRC_DIR=$OPENWRT_BASE/bin/ar71xx/packages
|
||||
PKG_DEST_SUBPATH=updates
|
||||
MAKE_INDEX_SCRIPT=$OPENWRT_BASE/scripts/ipkg-make-index.sh
|
||||
INDEX_FILE=Packages
|
||||
INDEX_GZ_FILE=Packages.gz
|
||||
DEVICE_TYPES="tl-mr3020 tl-wr703"
|
||||
MAX_GOOD_IMAGE_SIZE=3500000
|
||||
IMAGE_BASENAME="doodle3d-wifibox"
|
||||
|
||||
COMPRESS_RESULT=0
|
||||
PKG_DEST_BASE=.
|
||||
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
-h)
|
||||
echo "This script creates a directory with wifibox and ultifi ipk files found in the openWrt build environment."
|
||||
echo "The feed dir is called $PKG_DEST_DIR and will be created in the current directory. (currently `pwd`)"
|
||||
echo "If specified, the -z option also compresses the result for easier transfer to a webserver."
|
||||
exit
|
||||
;;
|
||||
-z)
|
||||
COMPRESS_RESULT=1
|
||||
;;
|
||||
-*)
|
||||
echo "Unrecognized option '$arg'"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
PKG_DEST_BASE=$arg
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
grep "^This is the buildsystem for the OpenWrt Linux distribution\.$" README >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Please run this script from the Openwrt build root (on OSX this is probably an image mounted under /Volumes)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
#determine the wifibox root path
|
||||
my_rel_dir=`dirname $0`
|
||||
pushd "$my_rel_dir" > /dev/null
|
||||
WIFIBOX_DIR="`pwd`/../.."
|
||||
popd > /dev/null
|
||||
|
||||
FW_VERSION=`cat $WIFIBOX_DIR/src/FIRMWARE-VERSION`
|
||||
echo "Compiling firmware update files for version ${FW_VERSION}"
|
||||
|
||||
|
||||
#setup paths
|
||||
PKG_DEST_DIR=$PKG_DEST_BASE/$PKG_DEST_SUBPATH
|
||||
PKG_FEED_DIR=$PKG_DEST_DIR/feed
|
||||
PKG_IMG_DIR=$PKG_DEST_DIR/images
|
||||
IMG_INDEX_FILE=$PKG_IMG_DIR/wifibox-image.index
|
||||
|
||||
if [ ! -d $PKG_DEST_DIR ]; then mkdir -p $PKG_DEST_DIR; fi
|
||||
echo "Using $PKG_DEST_DIR as target directory"
|
||||
|
||||
|
||||
#clear directory and copy package files
|
||||
if [ ! -d $PKG_FEED_DIR ]; then mkdir $PKG_FEED_DIR; fi
|
||||
cp $PKG_SRC_DIR/wifibox*.ipk $PKG_FEED_DIR
|
||||
cp $PKG_SRC_DIR/doodle3d-client*.ipk $PKG_FEED_DIR
|
||||
cp $PKG_SRC_DIR/print3d*.ipk $PKG_FEED_DIR
|
||||
cp $PKG_SRC_DIR/ultifi*.ipk $PKG_FEED_DIR
|
||||
rm -f $PKG_FEED_DIR/$INDEX_FILE
|
||||
rm -f $PKG_FEED_DIR/$INDEX_GZ_FILE
|
||||
|
||||
|
||||
#copy and rename images
|
||||
if [ ! -d $PKG_IMG_DIR ]; then mkdir $PKG_IMG_DIR; fi
|
||||
rm -f $IMG_INDEX_FILE
|
||||
for devtype in $DEVICE_TYPES; do
|
||||
IMG_SRC_PATH=$OPENWRT_BASE/bin/ar71xx
|
||||
if [ -f $IMG_SRC_PATH/openwrt-ar71xx-generic-${devtype}-v1-squashfs-sysupgrade.bin ]; then
|
||||
sysupgrade_name=$IMG_SRC_PATH/openwrt-ar71xx-generic-${devtype}-v1-squashfs-sysupgrade.bin
|
||||
factory_name=$IMG_SRC_PATH/openwrt-ar71xx-generic-${devtype}-v1-squashfs-factory.bin
|
||||
sysupgrade_size=`fileSize $sysupgrade_name`
|
||||
factory_size=`fileSize $factory_name`
|
||||
|
||||
echo "Copying images for device '${devtype}' (sysupgrade size: ${sysupgrade_size}, factory size: ${factory_size})"
|
||||
cp $sysupgrade_name $PKG_IMG_DIR/`constructImageName ${FW_VERSION} ${devtype} sysupgrade`
|
||||
cp $factory_name $PKG_IMG_DIR/`constructImageName ${FW_VERSION} ${devtype} factory`
|
||||
|
||||
if [ $sysupgrade_size -gt $MAX_GOOD_IMAGE_SIZE ]; then
|
||||
echo "WARNING: the sysupgrade image is larger than $MAX_GOOD_IMAGE_SIZE bytes, which probably means it will cause read/write problems when flashed to a device"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# ok this is ugly, but at least it generates a complete index (the loop assumes for each
|
||||
# sysupgrade image it finds, there is also a factory counterpart)
|
||||
for file in $PKG_IMG_DIR/${IMAGE_BASENAME}-*-sysupgrade.bin; do
|
||||
basefile=`basename ${file}`
|
||||
echo "Considering $basefile (${file})"
|
||||
# sorry for the shell magic
|
||||
devtype=${basefile:`expr ${#IMAGE_BASENAME} + 1`}
|
||||
version=${devtype//-*}
|
||||
devtype=${devtype#*-}
|
||||
devtype=${devtype%-*}
|
||||
generateIndexEntry $PKG_IMG_DIR $version $devtype >> $IMG_INDEX_FILE
|
||||
done
|
||||
|
||||
|
||||
# NOTE: the aliasing construct in the indexing script does not work (and even then, the md5 command defaults to a different output format), so we hack around it here.
|
||||
MD5_HACK_ENABLED=0
|
||||
which md5sum >/dev/null 2>&1
|
||||
if [ $? -eq 1 ]; then
|
||||
MD5_HACK_ENABLED=1
|
||||
TEMPBIN_DIR=/tmp/tempbin23QQDBR
|
||||
mkdir $TEMPBIN_DIR
|
||||
|
||||
cat <<EOF > $TEMPBIN_DIR/md5sum
|
||||
`type -p md5` -q \$1
|
||||
EOF
|
||||
|
||||
chmod +x $TEMPBIN_DIR/md5sum
|
||||
PATH=$PATH:$TEMPBIN_DIR
|
||||
fi
|
||||
|
||||
#this cwd juggling is required to have the package indexer generate correct paths (i.e. no paths) in the Packages file
|
||||
OPENWRT_DIR=`pwd`
|
||||
pushd $PKG_FEED_DIR > /dev/null
|
||||
$OPENWRT_DIR/$MAKE_INDEX_SCRIPT . > $PKG_FEED_DIR/$INDEX_FILE
|
||||
popd > /dev/null
|
||||
|
||||
if [ $MD5_HACK_ENABLED -eq 1 ]; then
|
||||
rm $TEMPBIN_DIR/md5sum
|
||||
rmdir $TEMPBIN_DIR
|
||||
fi
|
||||
|
||||
gzip -c $PKG_FEED_DIR/$INDEX_FILE > $PKG_FEED_DIR/$INDEX_GZ_FILE
|
||||
|
||||
|
||||
if [ $COMPRESS_RESULT -eq 1 ]; then
|
||||
cd $PKG_DEST_BASE
|
||||
echo "Compressing generated package directory..."
|
||||
tar czvf "doodle3d-wifibox-update-dist.tgz" $PKG_DEST_SUBPATH
|
||||
fi
|
526
extra/scripts/publish-wifibox-release.lua
Executable file
526
extra/scripts/publish-wifibox-release.lua
Executable file
@ -0,0 +1,526 @@
|
||||
#!/usr/bin/env lua
|
||||
--#!/usr/bin/env lua -l strict
|
||||
|
||||
-- This script creates a new release by copying openwrt image files and release notes to a local
|
||||
-- directory and updating the relevant index file with a new entry. This directory is
|
||||
-- then synchronized to the release repository online.
|
||||
--
|
||||
-- USAGE:
|
||||
-- The only dependency of this script is the penlight library, which can be installed using
|
||||
-- LuaRocks (http://luarocks.org/) as follows: 'sudo luarocks install penlight'.
|
||||
-- This script will automatically locate the Doodle3D repo's.
|
||||
-- Index files are fetched from the online repository.
|
||||
-- For synchronizing, rsync must have passwordless SSH access to the server, for a
|
||||
-- guide, see: http://www.linuxproblem.org/art_9.html.
|
||||
-- The scrips expects a alias named 'doodle3d.com', you can add this editing the following file:
|
||||
-- .ssh/config
|
||||
-- and adding
|
||||
-- Host doodle3d.com
|
||||
-- User webmaster@doodle3d.com
|
||||
-- HostName ftp.greenhost.nl
|
||||
-- Some basic sanity checks are built in (unique version, updated release notes, 'clean' openwrt config)
|
||||
-- but lots others are still missing (mainly: clean git repo's, freshly built images).
|
||||
-- The script must be run from within the openwrt build root. So it's handy to create a symlink
|
||||
-- to this file. You could to something like from the build root:
|
||||
-- ln -s ~/wrt-wifibox-feed/doodle3d-firmware/extra/scripts/publish-wifibox-release.lua .
|
||||
-- Then you can start with:
|
||||
-- cd trunk ../publish-wifibox-release.lua
|
||||
-- Before anything is actually uploaded, you will be asked if that's really what you want to do.
|
||||
-- It might be wise to make a backup on the server before updating it, there's a script
|
||||
-- to do this on the server: '~/backup-updates-dir.sh'.
|
||||
--
|
||||
-- To play around with or improve on this script, use and modify the variables 'SERVER_HOST'
|
||||
-- and 'SERVER_PATH' below to point to your machine (assuming you have a webserver running there).
|
||||
-- Also uncomment and modify UPDATER_BASE_URL. You will have to init the local 'repo' with at
|
||||
-- least empty index files ('wifibox-image.index' and 'wifibox-image.beta.index'), or you
|
||||
-- could of course mirror the online repository.
|
||||
--
|
||||
-- TODO (in random order):
|
||||
-- * (feature) command-line arguments: overrides, verbosity, allow local mirroring, clear local cache dir, etc.
|
||||
-- * (feature) automatically create a backup of the online repo (there's already a script fir this, as mentioned above)
|
||||
-- * (feature) check whether git repo's are clean and on correct branch
|
||||
-- * (feature) allow local mirroring with a reverse rsync command and rebuilding the indexes
|
||||
-- - update manager 'cache' should then be enabled to prevent fetchIndexTable from downloading files
|
||||
-- * (feature) automatically (re)build openwrt to ensure it is up to date?
|
||||
-- * (feature) update package feed (requires a local mirror for the feed indexing script)
|
||||
-- - in this case sanity checks must also be run on package versions/revisions
|
||||
-- * (feature) automatically tag (and merge?) git commits?
|
||||
-- * (feature) execute as dry-run by default so changes can be reviewed?
|
||||
-- * (refactor) rename awkward vars/funcs regarding entries, versions and caches...
|
||||
-- * (refactor) replace function arguments 'includeBetas' with a set function like setUseCache to improve readability
|
||||
-- * (refactor) replace prints with D() function from update manager or other slightly smarter mechanisms?
|
||||
|
||||
local function ERR(msg) print(msg) end
|
||||
|
||||
local ok, pl = pcall(require, 'pl.import_into')
|
||||
if not ok then
|
||||
ERR('This script requires the Penlight library')
|
||||
os.exit(2)
|
||||
end
|
||||
pl = pl()
|
||||
local um --- update manager module, will be loaded later through @{loadUpdateManager}
|
||||
|
||||
local lfs = require('lfs') -- assume this exists since it's required by penlight as well
|
||||
|
||||
|
||||
-----------------------------
|
||||
-- CONSTANTS AND VARIABLES --
|
||||
-----------------------------
|
||||
|
||||
--local SERVER_HOST = 'localhost'
|
||||
--local SERVER_PATH = '~USERDIR/public_html/wifibox/updates'
|
||||
--local UPDATER_BASE_URL = 'http://localhost/~USERDIR/wifibox/updates'
|
||||
local SERVER_HOST = 'doodle3d.com'
|
||||
local SERVER_PATH = 'doodle3d.com/DEFAULT/updates'
|
||||
--- SERVER_HOST and SERVER_PATH are used by rsync to merge the local working directory
|
||||
-- back into the online repository (requires functioning public key SSH access).
|
||||
-- UPDATER_BASE_URL is used by the d3d-updater script to download the index files
|
||||
-- (over HTTP), it defaults to the doodle3d.com online repo so it should only be
|
||||
-- used for development purposes.
|
||||
|
||||
local D3D_REPO_FIRMWARE_NAME = 'doodle3d-firmware'
|
||||
local D3D_REPO_CLIENT_NAME = 'doodle3d-client'
|
||||
local D3D_REPO_PRINT3D_NAME = 'print3d'
|
||||
local IMAGE_BASENAME = 'doodle3d-wifibox'
|
||||
local BACKUP_FILE_SUFFIX = 'bkp'
|
||||
local RELEASE_NOTES_FILE = "ReleaseNotes.md"
|
||||
local RSYNC_TIMEOUT = 2
|
||||
local MAX_VIABLE_IMAGE_SIZE = 3500000
|
||||
|
||||
local deviceType = 'tl-mr3020' -- or 'tl-wr703'
|
||||
local lock = nil
|
||||
local paths = {}
|
||||
|
||||
|
||||
|
||||
-----------------------
|
||||
-- UTILITY FUNCTIONS --
|
||||
-----------------------
|
||||
|
||||
local function loadUpdateManager()
|
||||
package.path = package.path .. ';' .. pl.path.join(paths.firmware, 'src') .. '/?.lua'
|
||||
local argStash = arg
|
||||
arg = nil
|
||||
um = require('script.d3d-updater') -- arg must be nil for the update manager to load as module
|
||||
arg = argStash
|
||||
end
|
||||
|
||||
local function quit(ev)
|
||||
if lock then lock:free() end
|
||||
os.exit(ev or 0)
|
||||
end
|
||||
|
||||
local function md5sum(file)
|
||||
local rv,_,sum = pl.utils.executeex('md5 -q "' .. file .. '"')
|
||||
|
||||
return rv and sum:sub(1, -2) or nil
|
||||
end
|
||||
|
||||
local function getYesNo(question)
|
||||
local answer
|
||||
repeat
|
||||
io.write(question)
|
||||
io.flush()
|
||||
answer = io.stdin:read('*line'):lower()
|
||||
until answer == 'yes' or answer == 'y' or answer == 'no' or answer == 'n'
|
||||
|
||||
return (answer:sub(1, 1) == 'y') and true or false
|
||||
end
|
||||
|
||||
local function detectRootPrivileges()
|
||||
local rv,_,userId = pl.utils.executeex('id -u')
|
||||
if not rv then return nil end
|
||||
|
||||
return tonumber(userId) == 0 and true or false
|
||||
end
|
||||
|
||||
local function findInFile(needle, file)
|
||||
local f = io.open(file, 'r')
|
||||
if not f then return nil,"could not open file" end
|
||||
|
||||
local t = f:read('*all')
|
||||
return not not t:find(needle, 1, true)
|
||||
end
|
||||
|
||||
local function detectOpenWrtRoot()
|
||||
local f = io.open('Makefile', 'r')
|
||||
local line = f and f:read('*line')
|
||||
local rv = (line and line:find('# Makefile for OpenWrt') == 1) and true or false
|
||||
|
||||
if f then f:close() end
|
||||
return rv
|
||||
end
|
||||
|
||||
-- returns uri (file path) of the wifibox feed, nil if not found or nil+msg on error
|
||||
-- recognized feed names are 'wifibox' and 'doodle3d' (case-insensitive)
|
||||
local function getWifiboxFeedRoot(feedsFile)
|
||||
local typ, nam, uri = nil, nil, nil
|
||||
local lineNo = 1
|
||||
local f = io.open(feedsFile, 'r')
|
||||
|
||||
if not f then return nil, "could not open '" .. feedsFile .. '"' end
|
||||
|
||||
for line in f:lines() do
|
||||
typ, nam, uri = line:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)$')
|
||||
|
||||
if not (typ and nam and uri) then
|
||||
f:close()
|
||||
return uri or nil, "could not parse line " .. feedsFile .. "#" .. lineNo
|
||||
end
|
||||
|
||||
local commented = (typ:find('#') == 1)
|
||||
if not commented and (nam:lower() == 'wifibox' or nam:lower() == 'doodle3d') then
|
||||
break
|
||||
else
|
||||
typ, nam, uri = nil, nil, nil
|
||||
end
|
||||
|
||||
lineNo = lineNo + 1
|
||||
end
|
||||
|
||||
if uri and not (typ == 'src-link' or typ == 'src-cpy') then return nil, "d3d feed has wrong type '" .. typ .. "', use 'src-link' or 'src-cpy'" end
|
||||
|
||||
f:close()
|
||||
return uri
|
||||
end
|
||||
|
||||
-- TODO: pass table to functions to fill in? if they all return either true or nil+msg, that could be used for display of ok/msg
|
||||
-- returns true on success, false on error, and displays meaningful messages
|
||||
--local function runCheck(msg, processFunc)
|
||||
-- io.write(msg .. "... ")
|
||||
-- return processFunc(--[[ hmm ]]--)
|
||||
--end
|
||||
|
||||
local function runAction(actMsg, errMsg, ev, func)
|
||||
io.write("* " .. actMsg .. "...")
|
||||
local rv,err = func()
|
||||
if not rv then
|
||||
if err then print("Error: " .. errMsg .. " (" .. err .. ")")
|
||||
else print("Error: " .. errMsg)
|
||||
end
|
||||
quit(ev)
|
||||
else
|
||||
print("ok")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function constructImageName(version, devType, sysupOrFactory)
|
||||
return IMAGE_BASENAME .. '-' .. um.formatVersion(version) .. '-' .. devType .. '-' .. sysupOrFactory .. '.bin'
|
||||
end
|
||||
|
||||
local function imageCachePath()
|
||||
return pl.path.join(paths.cache, 'images')
|
||||
end
|
||||
|
||||
local function ensureFilePresent(src, tgt)
|
||||
-- print("About to copy '" .. src .. "' => '" .. tgt .. "'")
|
||||
local srcMd5, tgtMd5 = md5sum(src), md5sum(tgt)
|
||||
|
||||
if not srcMd5 then return nil,"source file does not exist" end
|
||||
if tgtMd5 and srcMd5 ~= tgtMd5 then return nil,"target file already exists but is different from source file" end
|
||||
|
||||
if not tgtMd5 then
|
||||
if not pl.file.copy(src, tgt, false) then return nil,"could not copy file" end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
--------------------
|
||||
-- MAIN FUNCTIONS --
|
||||
--------------------
|
||||
|
||||
local function prepare()
|
||||
local msg = nil
|
||||
|
||||
io.write("* Checking if working directory is the OpenWrt root... ")
|
||||
local isOpenWrtRoot = detectOpenWrtRoot()
|
||||
if isOpenWrtRoot then
|
||||
paths.wrt = pl.path.currentdir()
|
||||
print("found " .. paths.wrt)
|
||||
else
|
||||
print("unrecognized directory, try changing directories or using -wrt-root")
|
||||
return nil
|
||||
end
|
||||
|
||||
io.write("* Looking for Doodle3D feed path... ")
|
||||
local d3dFeed,msg = getWifiboxFeedRoot('feeds.conf')
|
||||
if d3dFeed then
|
||||
print("found " .. d3dFeed)
|
||||
else
|
||||
if msg then print("not found: " .. msg) else print("not found.") end
|
||||
return nil
|
||||
end
|
||||
|
||||
paths.firmware = pl.path.join(d3dFeed, D3D_REPO_FIRMWARE_NAME)
|
||||
paths.client = pl.path.join(d3dFeed, D3D_REPO_CLIENT_NAME)
|
||||
paths.print3d = pl.path.join(d3dFeed, D3D_REPO_PRINT3D_NAME)
|
||||
|
||||
-- if empty, try to choose something sensible
|
||||
if not paths.cache or paths.cache == '' then
|
||||
--paths.cache = pl.app.appfile('')
|
||||
paths.cache = '/tmp/d3d-release-dir'
|
||||
end
|
||||
io.write("* Attempting to use " .. paths.cache .. " as cache dir... ")
|
||||
local rv,msg = pl.dir.makepath(paths.cache)
|
||||
if not rv then
|
||||
print("could not create path (" .. msg .. ").")
|
||||
return nil
|
||||
end
|
||||
|
||||
loadUpdateManager()
|
||||
|
||||
local rv,msg = pl.dir.makepath(imageCachePath())
|
||||
if not rv then
|
||||
print("could not create images dir (" .. msg .. ").")
|
||||
return nil
|
||||
end
|
||||
|
||||
lock,msg = lfs.lock_dir(paths.cache)
|
||||
if not lock then
|
||||
print("could not obtain directory lock (" .. msg .. ").")
|
||||
return nil
|
||||
else
|
||||
print("ok")
|
||||
end
|
||||
|
||||
-- initialize update manager script
|
||||
um.setUseCache(false)
|
||||
um.setVerbosity(1)
|
||||
um.setCachePath(imageCachePath())
|
||||
if type(UPDATER_BASE_URL) == 'string' and UPDATER_BASE_URL:len() > 0 then
|
||||
print("* Using updater base URL: '" .. UPDATER_BASE_URL .. "'")
|
||||
um.setBaseUrl(UPDATER_BASE_URL)
|
||||
else
|
||||
print("* Using updater base URL: d3d-updater default")
|
||||
end
|
||||
|
||||
print("* Using rsync server remote: '" .. SERVER_HOST .. "/" .. SERVER_PATH .. "'")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function collectLocalInfo()
|
||||
local info = {}
|
||||
|
||||
-- temporary fields required for copying image files
|
||||
info.factoryImgPath = pl.path.join(paths.wrt, 'bin/ar71xx/openwrt-ar71xx-generic-' .. deviceType .. '-v1-squashfs-factory.bin')
|
||||
info.sysupgradeImgPath = pl.path.join(paths.wrt, 'bin/ar71xx/openwrt-ar71xx-generic-' .. deviceType .. '-v1-squashfs-sysupgrade.bin')
|
||||
|
||||
info.version = um.parseVersion(pl.file.read(pl.path.join(paths.firmware, 'src/FIRMWARE-VERSION')))
|
||||
if not info.version then return nil,"could not determine current firmware version" end
|
||||
|
||||
info.factoryFileSize = pl.path.getsize(info.factoryImgPath)
|
||||
if not info.factoryFileSize then return nil,"could not determine size for factory image" end
|
||||
|
||||
info.sysupgradeFileSize = pl.path.getsize(info.sysupgradeImgPath)
|
||||
if not info.sysupgradeFileSize then return nil,"could not determine size for sysupgrade image" end
|
||||
|
||||
info.factoryMD5 = md5sum(info.factoryImgPath)
|
||||
info.sysupgradeMD5 = md5sum(info.sysupgradeImgPath)
|
||||
if not info.factoryMD5 or not info.sysupgradeMD5 then return nil,"could not determine MD5 sum for image(s)" end
|
||||
|
||||
info.factoryFilename = constructImageName(info.version, deviceType, 'factory')
|
||||
info.sysupgradeFilename = constructImageName(info.version, deviceType, 'sysupgrade')
|
||||
info.timestamp = os.time()
|
||||
|
||||
return info
|
||||
end
|
||||
|
||||
local function fetchVersionInfo()
|
||||
local msg,stables,betas = nil,nil,nil
|
||||
|
||||
stables,msg = um.getAvailableVersions('stables')
|
||||
if not stables then return nil,msg end
|
||||
|
||||
betas,msg = um.getAvailableVersions('betas')
|
||||
if not betas then return nil,msg end
|
||||
|
||||
return stables, betas
|
||||
end
|
||||
|
||||
local function generateIndex(newVersion, versionTable, isStable)
|
||||
local indexFilename = isStable and um.IMAGE_STABLE_INDEX_FILE or um.IMAGE_BETA_INDEX_FILE
|
||||
versionTable[#versionTable+1] = newVersion
|
||||
local sortedVers = pl.List(versionTable)
|
||||
sortedVers:sort(function(a, b)
|
||||
return um.compareVersions(a.version, b.version, a.timestamp, b.timestamp) < 0
|
||||
end)
|
||||
|
||||
local indexPath = pl.path.join(imageCachePath(), indexFilename)
|
||||
local rv = pl.file.copy(indexPath, pl.path.join(paths.cache, indexFilename..'.'..BACKUP_FILE_SUFFIX))
|
||||
if not rv then return nil,"could not backup index file" end
|
||||
|
||||
local idxFile = io.open(pl.path.join(imageCachePath(), indexFilename), 'w')
|
||||
if not idxFile then return nil,"could not open index file for writing" end
|
||||
|
||||
sortedVers:foreach(function(el)
|
||||
idxFile:write("Version: " .. um.formatVersion(el.version) .. "\n")
|
||||
idxFile:write("Files: " .. el.sysupgradeFilename .. "; " .. el.factoryFilename .. "\n")
|
||||
idxFile:write("FileSize: " .. el.sysupgradeFileSize .. "; " .. el.factoryFileSize .. "\n")
|
||||
idxFile:write("MD5: " .. el.sysupgradeMD5 .. "; " .. el.factoryMD5 .. "\n")
|
||||
if el.timestamp then idxFile:write("ReleaseDate: " .. um.formatDate(el.timestamp) .. "\n") end
|
||||
end)
|
||||
|
||||
idxFile:close()
|
||||
return 0
|
||||
end
|
||||
|
||||
local function copyImages(newVersion)
|
||||
local rv,msg
|
||||
rv,msg = ensureFilePresent(newVersion.factoryImgPath, pl.path.join(imageCachePath(), newVersion.factoryFilename))
|
||||
if not rv then return nil,msg end
|
||||
|
||||
rv,msg = ensureFilePresent(newVersion.sysupgradeImgPath, pl.path.join(imageCachePath(), newVersion.sysupgradeFilename))
|
||||
if not rv then return nil,msg end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function copyReleaseNotes(newVersion)
|
||||
local srcReleaseNotesPath = pl.path.join(paths.firmware, RELEASE_NOTES_FILE)
|
||||
local tgtReleaseNotesPath = pl.path.join(imageCachePath(), RELEASE_NOTES_FILE)
|
||||
|
||||
if not findInFile(um.formatVersion(newVersion.version), srcReleaseNotesPath) then
|
||||
return nil,"version not mentioned in release notes file"
|
||||
end
|
||||
|
||||
if pl.path.isfile(tgtReleaseNotesPath) then
|
||||
local rv = pl.file.copy(tgtReleaseNotesPath, tgtReleaseNotesPath..'.'..BACKUP_FILE_SUFFIX)
|
||||
if not rv then return nil,"could not backup file" end
|
||||
end
|
||||
|
||||
local rv = pl.file.copy(srcReleaseNotesPath, tgtReleaseNotesPath)
|
||||
if not rv then return nil,"could not copy file" end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- TODO: the packages are not really used and the openwrt script to generate the
|
||||
-- package index requires all packages to be present so this has been skipped for now
|
||||
local function buildFeedDir()
|
||||
local scriptPath = pl.path.join(paths.wrt, 'scripts/ipkg-make-index.sh')
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function uploadFiles()
|
||||
local serverUrl = SERVER_HOST..':'..SERVER_PATH
|
||||
-- rsync options are: recursive, preserve perms, symlinks and timestamps, be verbose and use compression
|
||||
local cmd = "rsync -rpltvz -e ssh --progress --timeout=" .. RSYNC_TIMEOUT .. " --exclude '*.bkp' --exclude 'lockfile.lfs' " .. paths.cache .. "/* " .. serverUrl
|
||||
print("Running command: '" .. cmd .. "'")
|
||||
local rv,ev = um.compatexecute(cmd)
|
||||
return rv and true or nil,("rsync failed, exit status: " .. ev)
|
||||
end
|
||||
|
||||
local function checkWrtConfig()
|
||||
local goodConfigPath = pl.path.join(paths.firmware, "extra/openwrt-build/openwrt-diffconfig-extramini")
|
||||
local wrtConfigPath = pl.path.tmpname()
|
||||
--print("diffonfig output file: " .. wrtConfigPath)
|
||||
|
||||
local rv,ev = pl.utils.execute('./scripts/diffconfig.sh > "' .. wrtConfigPath .. '" 2> /dev/null')
|
||||
if not rv then return nil,"could not run diffconfig script (exit status: " .. ev .. ")" end
|
||||
|
||||
local _,rv,output = pl.utils.executeex('diff "' .. wrtConfigPath .. '" "' .. goodConfigPath .. '"')
|
||||
|
||||
if rv == 0 then
|
||||
return true
|
||||
elseif rv == 1 then
|
||||
print("configurations differ:\n-----------------------\n" .. output .. "\n-----------------------")
|
||||
--ask for confirmation?
|
||||
else
|
||||
return nil,"unexpected exit status from diff (" .. rv .. ")"
|
||||
end
|
||||
end
|
||||
|
||||
local function main()
|
||||
print("\nDoodle3D release script")
|
||||
if detectRootPrivileges() then
|
||||
print("Error: refusing to run script as root.")
|
||||
quit(99)
|
||||
end
|
||||
|
||||
-- local opts = parseOptions(arg)
|
||||
--
|
||||
-- if opts['wrt-root'] then changedir(opts['wrt-root']) end
|
||||
-- if opts['cache-dir'] then paths.cache = opts['cache-dir'] end
|
||||
-- more options: clear cache, rebuild (download all and generate index from actual files), dry-run, force
|
||||
|
||||
if not prepare() then quit(1) end
|
||||
|
||||
|
||||
local newVersion,msg = collectLocalInfo()
|
||||
if not newVersion then
|
||||
print("Error: could not collect local version information (" .. msg .. ")")
|
||||
quit(3)
|
||||
end
|
||||
|
||||
local stables,betas = fetchVersionInfo()
|
||||
if not stables then
|
||||
print("Error: could not get version information (" .. betas .. ")")
|
||||
quit(1)
|
||||
end
|
||||
|
||||
--TODO: if requested, fetch images and packages (i.e., mirror whole directory)
|
||||
|
||||
|
||||
-- pl.pretty.dump(newVersion)
|
||||
-- print("stables: "); pl.pretty.dump(stables)
|
||||
-- print("===========================");
|
||||
-- print("betas: "); pl.pretty.dump(betas)
|
||||
|
||||
|
||||
print("\nRunning sanity checks")
|
||||
|
||||
runAction("Checking whether version is unique",
|
||||
"firmware version " .. um.formatVersion(newVersion.version) .. " already exists", 3, function()
|
||||
return not (um.findVersion(newVersion.version, nil, stables) or um.findVersion(newVersion.version, nil, betas)) and true or nil
|
||||
end)
|
||||
|
||||
runAction("Checking OpenWrt config", "failed", 3, checkWrtConfig)
|
||||
|
||||
--TODO: check git repos (`git log -n 1 --pretty=format:%ct` gives commit date of last commit (not author date))
|
||||
|
||||
local isStable = (newVersion.version.suffix == nil)
|
||||
print("\nRolling release for firmware version " .. um.formatVersion(newVersion.version) .. " (type: " .. (isStable and "stable" or "beta") .. ").")
|
||||
|
||||
if newVersion.sysupgradeFileSize > MAX_VIABLE_IMAGE_SIZE then
|
||||
print("Error: sysupgrade image file is too large, it will not run well (max. size: " .. MAX_VIABLE_IMAGE_SIZE .. " bytes)")
|
||||
quit(4)
|
||||
end
|
||||
|
||||
runAction("Copying release notes", "failed", 5, function()
|
||||
return copyReleaseNotes(newVersion)
|
||||
end)
|
||||
|
||||
runAction("Generating new index file", "could not generate index", 5, function()
|
||||
return generateIndex(newVersion, isStable and stables or betas, isStable)
|
||||
end)
|
||||
|
||||
runAction("Copying image files", "could not generate index", 5, function()
|
||||
return copyImages(newVersion)
|
||||
end)
|
||||
|
||||
io.write("* Building package feed directory...")
|
||||
print("skipped - not implemented")
|
||||
-- runAction("Building package feed directory", "failed", 5, buildFeedDir)
|
||||
|
||||
|
||||
local answer = getYesNo("? Local updates cache will be synced to remote server, proceed? (y/n) ")
|
||||
if answer ~= true then
|
||||
print("Did not get green light, quitting.")
|
||||
quit(5)
|
||||
end
|
||||
|
||||
runAction("About to sync files to server", "could not upload files", 6, uploadFiles)
|
||||
|
||||
print("Done.")
|
||||
quit()
|
||||
end
|
||||
|
||||
main()
|
@ -1,54 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script copies the packages feed & images directory to ~/Sites (e.g. for use with XAMPP or the like).
|
||||
# Using the -u option, it can also upload to doodle3d.com/updates (make sure ssh automatically uses the correct username, or change the rsync command below).
|
||||
# Modify WIFIBOX_BASE_DIR to point to your wifibox directory tree.
|
||||
|
||||
WIFIBOX_BASE_DIR=~/Files/_devel/eclipse-workspace/wifibox
|
||||
DEST_DIR=~/Sites/wifibox
|
||||
UPDATES_DIR=updates
|
||||
BASE_URL=doodle3d.com
|
||||
|
||||
OPTIONS=$DEST_DIR
|
||||
UPLOAD_FILES=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
-h)
|
||||
echo "This script calls 'create-wifibox-updates-dir.sh' to generate feed/image directories in $DEST_DIR"
|
||||
echo "Use '-z' to also create a compressed file containing the 'updates' directory."
|
||||
echo "Used '-u' to also upload the directory to doodle3d.com/updates"
|
||||
exit
|
||||
;;
|
||||
-z)
|
||||
OPTIONS="$OPTIONS -z"
|
||||
;;
|
||||
-u)
|
||||
UPLOAD_FILES=1
|
||||
;;
|
||||
*)
|
||||
echo "Unrecognized option '$arg'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
$WIFIBOX_BASE_DIR/extra/scripts/create-wifibox-updates-dir.sh $OPTIONS
|
||||
RETURN_VALUE=$?
|
||||
if [ $RETURN_VALUE -ne 0 ]; then
|
||||
echo "create-wifibox-updates-dir.sh returned an error (${RETURN_VALUE}), exiting"
|
||||
exit $RETURN_VALUE
|
||||
fi
|
||||
|
||||
if [ $UPLOAD_FILES -eq 1 ]; then
|
||||
#UPLOAD_PATH=$BASE_URL:public_html/updates
|
||||
UPLOAD_PATH=$BASE_URL:doodle3d.com/DEFAULT/updates
|
||||
|
||||
cat <<-'EOM' > $DEST_DIR/$UPDATES_DIR/.htaccess
|
||||
Options +Indexes
|
||||
EOM
|
||||
|
||||
echo "Uploading files to $UPLOAD_PATH (if you are asked for your password, please add an entry to your ~/.ssh/config and upload your public ssh key)"
|
||||
#options are: recursive, preserve perms, symlinks and timestamps, be verbose and use compression
|
||||
rsync -rpltvz -e ssh --progress $DEST_DIR/$UPDATES_DIR/.htaccess $DEST_DIR/$UPDATES_DIR/* $UPLOAD_PATH
|
||||
fi
|
@ -77,15 +77,20 @@ $IPKG_INSTROOT/etc/init.d/wifibox start
|
||||
$IPKG_INSTROOT/etc/init.d/dhcpcheck enable
|
||||
|
||||
if [ -z "$IPKG_INSTROOT" ]; then
|
||||
echo "Enabling wifi device..."
|
||||
echo "Enabling and configuring wifi device..."
|
||||
uci set wireless.@wifi-device[0].disabled=0
|
||||
uci set wireless.radio0.country='NL'
|
||||
uci commit wireless; wifi
|
||||
|
||||
echo "Disabling default route and DNS server for lan network interface..."
|
||||
uci set dhcp.lan.dhcp_option='3 6'
|
||||
uci commit dhcp; /etc/init.d/dnsmasq reload
|
||||
|
||||
addFirewallNet
|
||||
|
||||
echo "Adding network interface 'wlan'..."
|
||||
uci set network.wlan=interface; uci commit network; /etc/init.d/network reload
|
||||
uci set network.wlan=interface
|
||||
uci commit network; /etc/init.d/network reload
|
||||
|
||||
else
|
||||
# Create a script to setup the system as wifibox, it will be deleted after it has been run, except if it returns > 0
|
||||
@ -102,6 +107,8 @@ else
|
||||
uci set wireless.radio0.country='NL'
|
||||
# TODO: add firewall net
|
||||
uci set network.wlan=interface
|
||||
|
||||
uci set dhcp.lan.dhcp_option='3 6'
|
||||
EOM
|
||||
|
||||
echo "WARNING: WiFiBox network configuration can only be fully prepared when installing on real device"
|
||||
|
@ -1 +1 @@
|
||||
0.10.1
|
||||
0.10.2
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,18 +360,33 @@ 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)
|
||||
M.switchConfiguration({ wifiiface="add", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm" })
|
||||
|
||||
-- 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
|
||||
-- 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
|
||||
|
||||
M.setStatus(M.CONNECTED,"Connected");
|
||||
|
||||
-- signin to connect.doodle3d.com
|
||||
local success, output = signin.signin()
|
||||
if success then
|
||||
@ -389,6 +395,9 @@ function M.associateSsid(ssid, passphrase, recreate, boot)
|
||||
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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,7 +146,7 @@ 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 = ""
|
||||
@ -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)
|
||||
|
||||
|
@ -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 .. ")")
|
||||
@ -156,6 +163,9 @@ function M.install_POST(request, response)
|
||||
|
||||
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 .. ")")
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
58
test/test-d3d-updater.lua
Normal file
58
test/test-d3d-updater.lua
Normal file
@ -0,0 +1,58 @@
|
||||
-- This script contains a number of impromptu tests to check version comparisons, kept around in case real unit tests will be created one day.
|
||||
argStash = arg
|
||||
arg = nil
|
||||
local upd = require('d3d-updater')
|
||||
arg = argStash
|
||||
|
||||
local function dump(o)
|
||||
if type(o) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(o) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. dump(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(o)
|
||||
end
|
||||
end
|
||||
|
||||
local input = '19990213'
|
||||
local ts = upd.parseDate(input)
|
||||
print("parse " .. input .. ": " .. ts)
|
||||
print("format " .. ts .. ": " .. upd.formatDate(ts))
|
||||
|
||||
local vertex1 = '0.2.3'
|
||||
local vertab1 = upd.parseVersion(vertex1)
|
||||
print("parse " .. vertex1 .. ": " .. dump(vertab1))
|
||||
print("formatted: " .. upd.formatVersion(vertab1))
|
||||
|
||||
local vertex2 = '0.2.3-text'
|
||||
local vertab2 = upd.parseVersion(vertex2)
|
||||
print("parse " .. vertex2 .. ": " .. dump(vertab2))
|
||||
print("formatted: " .. upd.formatVersion(vertab2))
|
||||
|
||||
local vA, vB = upd.parseVersion("1.4.5-alpha"), upd.parseVersion("1.4.4-rc1")
|
||||
local tsA, tsB = 100000, 100005
|
||||
|
||||
local cmp1,cmp2 = upd.compareVersions(vA, vB)
|
||||
print("vA <=> vB: " .. cmp1 .. " / " .. dump(cmp2))
|
||||
cmp1,cmp2 = upd.compareVersions(vA, vB, tsA, tsB)
|
||||
print("vA/tsA <=> vB/tsB: " .. cmp1 .. " / " .. dump(cmp2))
|
||||
cmp1,cmp2 = upd.compareVersions(vB, vA, tsA, tsB)
|
||||
print("vB/tsA <=> vA/tsB: " .. cmp1 .. " / " .. dump(cmp2))
|
||||
cmp1,cmp2 = upd.compareVersions(vA, vB, tsB, tsA)
|
||||
print("vA/tsB <=> vB/tsA: " .. cmp1 .. " / " .. dump(cmp2))
|
||||
|
||||
local vWithout,vWith = upd.parseVersion('1.2.3'), upd.parseVersion('1.2.3-sfx')
|
||||
--print("vWithout: " .. dump(vWithout) .. "; vWith: " .. dump(vWith))
|
||||
cmp1,cmp2 = upd.compareVersions(vWithout, vWithout)
|
||||
print("1.2.3 <=> 1.2.3: " .. cmp1 .. " / " .. dump(cmp2))
|
||||
cmp1,cmp2 = upd.compareVersions(vWithout, vWith)
|
||||
print("1.2.3 <=> 1.2.3-sfx: " .. cmp1 .. " / " .. dump(cmp2))
|
||||
cmp1,cmp2 = upd.compareVersions(vWith, vWith)
|
||||
print("1.2.3-sfx <=> 1.2.3-sfx: " .. cmp1 .. " / " .. dump(cmp2))
|
||||
|
||||
print("nn equal? " .. dump(upd.versionsEqual(vWithout, vWithout)))
|
||||
print("ny equal? " .. dump(upd.versionsEqual(vWithout, vWith)))
|
||||
print("yy equal? " .. dump(upd.versionsEqual(vWith, vWith)))
|
Loading…
Reference in New Issue
Block a user