Self Updating Package Script
Posted: Sat Feb 15, 2014 2:10 am
by Jor'Mox
So, someone requested a copy of my self updating package script, so here it is. Please keep in mind that it is still a work in progress, and this isn't stripped down to be generic (so it is still structured to be part of the larger script package that I built and maintain for a game I play). Basically, it grabs a version check file from a remote server, compares the versions listed there with the versions stored locally, and then downloads any files that are out of date. Once all files are downloaded, all of the files are loaded in sequence, using doFile. Comments and suggestions are welcome.
-- DSL PNP 4.0 Main Script
-- By: Zachary Hiland
-- 2/09/2014
-- Not actually hosted, shown as included in testing.xml
dslpnp = dslpnp or {}
dslpnp.update = dslpnp.update or {}
dslpnp.config = dslpnp.config or {}
local defaults = {
general_scripts = {
"Gauges",
"Window Manager"
},
basic_scripts = {
"growl",
"triggers",
"aliases",
"timers",
"fileIO",
"extensions",
"data",
},
scripts_list = {
"borders",
"statusbar",
"support",
"filesend",
"character",
},
initialize = {
"borders",
"statusbar",
"support",
"filesend",
"character",
},
connect_events = {
"resetTimers",
"resetTriggers",
"resetAliases",
},
file_path = getMudletHomeDir() .. "/PNP/",
download_path = "http://shatteredcloud.com/scripts/",
package_name = "testing",
version_check_download = "versions.txt",
version_check_save = "version_check.txt",
versions_file_name = "versions.txt",
}
local download_queue = download_queue or {}
local downloading = false
local available = {}
local unavailable = {}
local scripts_list = {}
local init_list = {}
local event_list = {}
-- Creates a duplicate of the given table
-- This is a local copy of table.copy used by table.update
local function table_copy(tbl)
local new = {}
for k,v in pairs(tbl) do
if type(v) == "table" then
new[k] = table_copy(v)
else
new[k] = v
end
end
return new
end
-- Updates the first table with values from the second table
-- This is a local copy of table.update as it will function in the next Mudlet release
local function table_update(t1, t2)
local tbl = table_copy(t1)
for k,v in pairs(t2) do
if type(v) == "table" then
tbl[k] = table_update(tbl[k] or {}, v)
else
tbl[k] = v
end
end
return tbl
end
local function fileOpen(filename,mode)
local errors
mode = mode or "read"
assert(table.contains({"read","write","append","modify"},mode),"Invalid mode: must be 'read', 'write', 'append', or 'modify'.")
if mode ~= "write" then
local info = lfs.attributes(filename)
if not info then
errors = "Invalid filename: no such file."
return nil, errors
end
if info.mode ~= "file" then
errors = "Invalid filename: path points to a directory."
return nil, errors
end
end
local file = {}
file.name = filename
file.mode = mode
file.type = "fileIO_file"
file.contents = {}
if file.mode == "read" or file.mode == "modify" then
local tmp = io.open(file.name,"r")
local linenum = 1
for line in tmp:lines() do
file.contents[linenum] = line
linenum = linenum + 1
end
tmp:close()
end
setmetatable(file,dslpnp.fileIO)
return file, nil
end
local function fileClose(file)
assert(file.type == "fileIO_file", "Invalid file: must be file returned by fileIO.open.")
local tmp
if file.mode == "write" then
tmp = io.open(file.name,"w")
elseif file.mode == "append" then
tmp = io.open(file.name,"a")
elseif file.mode == "modify" then
tmp = io.open(file.name,"w+")
end
if tmp then
for k,v in ipairs(file.contents) do
tmp:write(v .. "\n")
end
tmp:flush()
tmp:close()
tmp = nil
end
return true
end
-- THIS FUNCTION INITIALIZES SCRIPTS, GETS CALLED ON CONNECT/RECONNECT
function dslpnp.initialize()
init_list = table.n_union(defaults.initialize, dslpnp.config.initialize or {})
event_list = table.n_union(defaults.connect_events, dslpnp.config.connect_events or {})
-- run required events
for k,v in ipairs(event_list) do
raiseEvent(v)
end
-- initialize scripts
for k,v in ipairs(init_list) do
raiseEvent("onConfig",v)
end
end
local function load_package()
-- uninstall old package
uninstallPackage(defaults.package_name)
-- install new package
installPackage(defaults.file_path .. defaults.package_name .. ".xml")
end
local function check_available()
for k, v in ipairs(scripts_list) do
-- check if script is listed in available or unavailable table
if (not table.contains(available, v)) and (not table.contains(unavailable,v)) then
-- if script not accounted for, not all scripts are loaded, return false
return false
end
end
-- if all scripts accounted for, return true
return true
end
local function load_scripts()
local path, name
if check_available() then
for k, v in ipairs(available) do
if table.contains(unavailable,v) then
table.remove(unavailable,table.index_of(unavailable, v))
end
path = defaults.file_path .. v .. ".lua"
name = string.gsub(v,"DSL_PNP_", "")
if io.exists(path) then
dofile(path)
print("Script: " .. name .. " loaded successfully.")
else
print("Script: " .. name .. " does not exist.")
end
end
echo("\n")
for k,v in ipairs(unavailable) do
name = string.gsub(v,"DSL_PNP_", "")
print("Script: " .. name .. " not loaded.")
end
echo("\n")
dslpnp.initialize()
end
end
local function start_download()
-- get info from queue
local info = download_queue[1]
if info then
local path, address = info[1], info[2]
-- remove current item from queue
table.remove(download_queue,1)
-- begin download
downloadFile(path,address)
downloading = true
else
load_scripts()
end
end
local function queue_download(path, address)
-- add item to queue
table.insert(download_queue, {path, address})
if not downloading then
-- start new download if none in progress
start_download()
end
end
local function get_version_check()
-- create PNP folder in Mudlet home directory if necessary
lfs.mkdir(getMudletHomeDir() .. "/PNP")
-- download current version info
queue_download(defaults.file_path .. defaults.version_check_save, defaults.download_path .. defaults.version_check_download)
end
local function check_versions()
local version_path = defaults.file_path .. defaults.versions_file_name
local check_path = defaults.file_path .. defaults.version_check_save
local new_version, old_version
local check_file, version_file
local found
-- read in check file
check_file = fileOpen(check_path,"read")
if io.exists(version_path) then
-- read in version file
version_file = fileOpen(version_path,"modify")
else
-- create new version file
version_file = fileOpen(version_path,"write")
end
-- check versions for all loaded scripts
for k, v in ipairs(scripts_list) do
new_version = nil
found = false
-- find new version info for current script
for k2, v2 in ipairs(check_file.contents) do
if string.find(v2,v) then
found = true
new_version = string.match(v2,"[%w%.]+ : ([%w%.]+)")
old_version = nil
-- find old version info for current script
for k3, v3 in ipairs(version_file.contents) do
if string.find(v3,v) then
old_version = string.match(v3,"[%w%.]+ : ([%w%.]+)")
-- update old version info
version_file.contents[k3] = v2
break
end
end
-- compare version info
if new_version ~= old_version then
-- download new version of old script
queue_download(defaults.file_path .. v .. ".lua", defaults.download_path .. v .. ".lua")
if not old_version then
-- insert missing version info
table.insert(version_file.contents, v2)
end
else
table.insert(available, v)
end
break
end
end
-- scripts not found are listed as unavailable
if not found then table.insert(unavailable, v) end
end
-- write new version info to file
fileClose(version_file)
-- close version check file
fileClose(check_file)
-- try to load scripts
load_scripts()
end
local function finish_download(path)
-- start next download in queue
if download_queue[1] then
start_download()
else
downloading = false
end
-- run version checking once file downloaded
if string.find(path,defaults.version_check_save) then
check_versions()
elseif string.find(path,".xml") then
load_package()
else
for k, v in ipairs(scripts_list) do
if string.find(path,v) then
table.insert(available,v)
break
end
end
if not downloading then
load_scripts()
end
end
end
local function fail_download(...)
-- begin next download
start_download()
-- add failed download to list of unavailable scripts
table.insert(unavailable, arg[1])
end
function dslpnp.update.update_package()
-- create PNP folder in Mudlet home directory if necessary
lfs.mkdir(getMudletHomeDir() .. "/PNP")
-- download newest package
queue_download(defaults.file_path .. defaults.package_name .. ".xml", defaults.download_path .. defaults.package_name .. ".xml")
end
function dslpnp.update.update_scripts()
available = {}
unavailable = {}
dslpnp.config.basic_scripts = table_update(defaults.basic_scripts, dslpnp.config.basic_scripts or {})
dslpnp.config.scripts_list = dslpnp.config.scripts_list or defaults.scripts_list or {}
dslpnp.config.general_scripts = table_update(defaults.general_scripts, dslpnp.config.general_scripts or {})
scripts_list = table.n_union(dslpnp.config.basic_scripts, dslpnp.config.scripts_list)
for k,v in ipairs(scripts_list) do
scripts_list[k] = "DSL_PNP_" .. string.title(v)
end
scripts_list = table.n_union(scripts_list, dslpnp.config.general_scripts)
get_version_check()
end
function dslpnp.update.clear_versions()
local version_path = defaults.file_path .. defaults.versions_file_name
if io.exists(version_path) then
-- read in version file
version_file = fileOpen(version_path,"modify")
version_file.contents = {}
fileClose(version_file)
end
end
function dslpnp.update.eventHandler(event, ...)
if event == "sysConnectionEvent" then
dslpnp.update.update_scripts()
elseif event == "sysDownloadDone" then
finish_download(...)
elseif event == "sysDownloadError" then
fail_download(...)
end
end
registerAnonymousEventHandler("sysConnectionEvent","dslpnp.update.eventHandler")
registerAnonymousEventHandler("sysDownloadDone", "dslpnp.update.eventHandler")
registerAnonymousEventHandler("sysDownloadError", "dslpnp.update.eventHandler")