2116 lines
72 KiB
Lua
2116 lines
72 KiB
Lua
-- MojoSetup; a portable, flexible installation application.
|
|
--
|
|
-- Please see the file LICENSE.txt in the source's root directory.
|
|
--
|
|
-- This file written by Ryan C. Gordon.
|
|
|
|
|
|
-- This is where most of the magic happens. Everything is initialized, and
|
|
-- the user's config script has successfully run. This Lua chunk drives the
|
|
-- main application code.
|
|
|
|
-- !!! FIXME: add a --dryrun option.
|
|
|
|
local _ = MojoSetup.translate
|
|
|
|
MojoSetup.metadatakey = ".mojosetup_metadata."
|
|
MojoSetup.metadatadesc = _("Metadata")
|
|
MojoSetup.metadatadirname = ".mojosetup"
|
|
|
|
if MojoSetup.info.platform == "windows" then
|
|
MojoSetup.controlappname = "mojosetup.exe"
|
|
else
|
|
MojoSetup.controlappname = "mojosetup"
|
|
end
|
|
|
|
|
|
local function badcmdline()
|
|
MojoSetup.fatal(_("Invalid command line"))
|
|
end
|
|
|
|
|
|
-- This currently counts on (base) ending with a single "/", and not having
|
|
-- any strange distortions: "/blah/../blah/.//" would not match "/blah/x",
|
|
-- although it _should_ if we parsed it out.
|
|
local function make_relative(fname, base)
|
|
local baselen = string.len(base)
|
|
if string.sub(fname, 0, baselen) == base then
|
|
fname = string.sub(fname, baselen+2) -- make it relative.
|
|
end
|
|
return fname
|
|
end
|
|
|
|
|
|
local function manifest_resync(man, fname, _key)
|
|
if fname == nil then return end
|
|
|
|
local fullpath = fname
|
|
fname = make_relative(fname, MojoSetup.destination)
|
|
|
|
if man[fname] == nil then
|
|
MojoSetup.logwarning("Tried to resync unknown file '" ..fname.. "' in manifest!")
|
|
else
|
|
local perms = "0644" -- !!! FIXME MojoSetup.platform.perms(fullpath)
|
|
local sums = nil
|
|
local lndest = nil
|
|
local ftype = nil
|
|
|
|
if MojoSetup.platform.issymlink(fullpath) then
|
|
ftype = "symlink"
|
|
-- !!! FIXME: linkdest?
|
|
elseif MojoSetup.platform.isdir(fullpath) then
|
|
ftype = "dir"
|
|
else -- !!! FIXME: other types?
|
|
ftype = "file"
|
|
sums = MojoSetup.checksum(fullpath)
|
|
end
|
|
|
|
man[fname] =
|
|
{
|
|
key = _key,
|
|
type = ftype,
|
|
mode = perms,
|
|
checksums = sums,
|
|
linkdest = lndest
|
|
}
|
|
|
|
MojoSetup.logwarning("Resync'd file '" ..fname.. "' in manifest")
|
|
end
|
|
end
|
|
|
|
|
|
-- !!! FIXME: I need to go back from managing everything installed through
|
|
-- !!! FIXME: the manifest to a separate table of "things written to disk
|
|
-- !!! FIXME: on just this run" ... the existing manifest code can stay as-is,
|
|
-- !!! FIXME: rollbacks should be done exclusively from that other table.
|
|
-- !!! FIXME: Right now we're relying on dumb stuff like sorting the filenames
|
|
-- !!! FIXME: to get a safe deletion order on revert, but we should just
|
|
-- !!! FIXME: keep a chronological array instead.
|
|
|
|
local function manifest_add(man, fname, _key, ftype, mode, sums, lndest)
|
|
if (fname ~= nil) and (_key ~= nil) then
|
|
local destlen = string.len(MojoSetup.destination)
|
|
if string.sub(fname, 0, destlen) == MojoSetup.destination then
|
|
fname = string.sub(fname, destlen+2) -- make it relative.
|
|
end
|
|
|
|
if man[fname] ~= nil then
|
|
MojoSetup.logwarning("Overwriting file '" .. fname .. "' in manifest!")
|
|
end
|
|
|
|
man[fname] = {
|
|
key = _key,
|
|
type = ftype,
|
|
mode = perms,
|
|
checksums = sums,
|
|
linkdest = lndest
|
|
}
|
|
end
|
|
end
|
|
|
|
|
|
local function manifest_delete(man, fname)
|
|
if fname ~= nil then
|
|
local destlen = string.len(MojoSetup.destination)
|
|
if string.sub(fname, 0, destlen) == MojoSetup.destination then
|
|
fname = string.sub(fname, destlen+2) -- make it relative.
|
|
end
|
|
|
|
if man[fname] == nil then
|
|
MojoSetup.logwarning("Deleting unknown file '" .. fname .. "' from manifest!")
|
|
else
|
|
man[fname] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function flatten_list(list)
|
|
local retval = list
|
|
if type(list) == "table" then
|
|
retval = {}
|
|
for i,v in ipairs(list) do
|
|
if #retval == 0 then
|
|
retval[1] = v
|
|
else
|
|
retval[#retval+1] = ';'
|
|
retval[#retval+1] = v
|
|
end
|
|
end
|
|
retval = table.concat(retval)
|
|
end
|
|
return retval
|
|
end
|
|
|
|
|
|
local function do_delete(fname)
|
|
local retval = false
|
|
if fname == nil then
|
|
retval = true
|
|
else
|
|
-- Try to unlink() first, so we'll catch broken symlinks, then try
|
|
-- exists(): if it really wasn't there, we'll call it success anyhow.
|
|
if MojoSetup.platform.unlink(fname) then
|
|
MojoSetup.loginfo("Deleted '" .. fname .. "'")
|
|
retval = true
|
|
elseif not MojoSetup.platform.exists(fname) then
|
|
retval = true
|
|
else
|
|
MojoSetup.logerror("Deleting '" .. fname .. "'" .. " failed!")
|
|
end
|
|
end
|
|
return retval
|
|
end
|
|
|
|
|
|
local function delete_files(filelist, callback, error_is_fatal)
|
|
if filelist ~= nil then
|
|
local max = #filelist
|
|
for i = max,1,-1 do
|
|
local fname = filelist[i]
|
|
if not do_delete(fname) and error_is_fatal then
|
|
MojoSetup.fatal(_("Deletion failed!"))
|
|
end
|
|
|
|
if callback ~= nil then
|
|
callback(fname, i, max)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function delete_rollbacks()
|
|
if MojoSetup.rollbacks == nil then
|
|
return
|
|
end
|
|
local fnames = {}
|
|
local max = #MojoSetup.rollbacks
|
|
for id = 1,max,1 do
|
|
fnames[id] = MojoSetup.rollbackdir .. "/" .. id
|
|
end
|
|
MojoSetup.rollbacks = {} -- just in case this gets called again...
|
|
delete_files(fnames) -- !!! FIXME: callback for gui queue pump?
|
|
end
|
|
|
|
local function delete_scratchdirs()
|
|
do_delete(MojoSetup.downloaddir)
|
|
do_delete(MojoSetup.rollbackdir)
|
|
do_delete(MojoSetup.scratchdir) -- must be after dirs it contains!
|
|
do_delete(MojoSetup.metadatadir) -- must be last! (and is okay to fail.)
|
|
end
|
|
|
|
|
|
local function do_rollbacks()
|
|
if MojoSetup.rollbacks == nil then
|
|
return
|
|
end
|
|
|
|
local max = #MojoSetup.rollbacks
|
|
for id = max,1,-1 do
|
|
local src = MojoSetup.rollbackdir .. "/" .. id
|
|
local dest = MojoSetup.rollbacks[id]
|
|
if not MojoSetup.movefile(src, dest) then
|
|
-- we're already in fatal(), so we can only throw up a msgbox...
|
|
MojoSetup.msgbox(_("Serious problem"),
|
|
_("Couldn't restore some files. Your existing installation is likely damaged."))
|
|
end
|
|
MojoSetup.loginfo("Restored rollback #" .. id .. ": '" .. src .. "' -> '" .. dest .. "'")
|
|
end
|
|
|
|
MojoSetup.rollbacks = {} -- just in case this gets called again...
|
|
end
|
|
|
|
|
|
-- get a linear array of filenames in the manifest.
|
|
local function flatten_manifest(man, postprocess)
|
|
local files = {}
|
|
if postprocess == nil then
|
|
postprocess = function(x) return x end
|
|
end
|
|
if man ~= nil then
|
|
for fname,items in pairs(man) do
|
|
files[#files+1] = postprocess(fname)
|
|
end
|
|
end
|
|
|
|
table.sort(files, function(a,b) return MojoSetup.strcmp(a,b) < 0 end)
|
|
return files
|
|
end
|
|
|
|
|
|
local function prepend_dest_dir(fname)
|
|
if fname == "" then
|
|
return MojoSetup.destination
|
|
end
|
|
return MojoSetup.destination .. "/" .. fname
|
|
end
|
|
|
|
|
|
-- This gets called by fatal()...must be a global function.
|
|
function MojoSetup.revertinstall()
|
|
-- (The real revertinstall is set later. This is a stub for startup.)
|
|
end
|
|
|
|
|
|
local function calc_percent(current, total)
|
|
if total == 0 then
|
|
return 0
|
|
elseif total < 0 then
|
|
return -1
|
|
end
|
|
|
|
local retval = MojoSetup.truncatenum((current / total) * 100)
|
|
if retval > 100 then
|
|
retval = 100
|
|
elseif retval < 0 then
|
|
retval = 0
|
|
end
|
|
return retval
|
|
end
|
|
|
|
|
|
local function make_rate_string(rate, bw, total)
|
|
local retval = nil
|
|
|
|
if rate <= 0 then
|
|
retval = _("stalled")
|
|
else
|
|
local ratetype = _("B/s")
|
|
if rate > 1024 then
|
|
rate = MojoSetup.truncatenum(rate / 1024)
|
|
ratetype = _("KB/s")
|
|
end
|
|
|
|
if total > 0 then -- can approximate time left if we know the goal.
|
|
local bytesleft = total - bw
|
|
local secsleft = MojoSetup.truncatenum(bytesleft / rate)
|
|
local minsleft = MojoSetup.truncatenum(secsleft / 60)
|
|
local hoursleft = MojoSetup.truncatenum(minsleft / 60)
|
|
|
|
secsleft = string.sub("00" .. (secsleft - (minsleft * 60)), -2)
|
|
minsleft = string.sub("00" .. (minsleft - (hoursleft * 60)), -2)
|
|
|
|
if hoursleft < 10 then
|
|
hoursleft = "0" .. hoursleft
|
|
else
|
|
hoursleft = tostring(hoursleft)
|
|
end
|
|
|
|
retval = MojoSetup.format(_("%0 %1, %2:%3:%4 remaining"),
|
|
rate, ratetype,
|
|
hoursleft, minsleft, secsleft)
|
|
else
|
|
retval = MojoSetup.format(_("%0 %1"), rate, ratetype)
|
|
end
|
|
end
|
|
|
|
return retval
|
|
end
|
|
|
|
|
|
local function split_path(path)
|
|
local retval = {}
|
|
for item in string.gmatch(path .. "/", "(.-)/") do
|
|
if item ~= "" then
|
|
retval[#retval+1] = item
|
|
end
|
|
end
|
|
return retval
|
|
end
|
|
|
|
local function rebuild_path(paths, n)
|
|
local retval = paths[n]
|
|
n = n + 1
|
|
while paths[n] ~= nil do
|
|
retval = retval .. "/" .. paths[n]
|
|
n = n + 1
|
|
end
|
|
return retval
|
|
end
|
|
|
|
local function normalize_path(path)
|
|
return rebuild_path(split_path(path), 1)
|
|
end
|
|
|
|
|
|
local function close_archive_list(arclist)
|
|
for i = #arclist,1,-1 do
|
|
MojoSetup.archive.close(arclist[i])
|
|
arclist[i] = nil
|
|
end
|
|
end
|
|
|
|
|
|
-- This code's a little nasty...
|
|
local function drill_for_archive(archive, path, arclist)
|
|
if not MojoSetup.archive.enumerate(archive) then
|
|
MojoSetup.fatal(_("Couldn't enumerate archive"))
|
|
end
|
|
|
|
local pathtab = split_path(path)
|
|
local ent = MojoSetup.archive.enumnext(archive)
|
|
while ent ~= nil do
|
|
if ent.type == "file" then
|
|
local i = 1
|
|
local enttab = split_path(ent.filename)
|
|
while (enttab[i] ~= nil) and (enttab[i] == pathtab[i]) do
|
|
i = i + 1
|
|
end
|
|
|
|
if enttab[i] == nil then
|
|
-- It's a file that makes up some of the specified path.
|
|
-- open it as an archive and keep drilling...
|
|
local arc = MojoSetup.archive.fromentry(archive)
|
|
if arc == nil then
|
|
MojoSetup.fatal(_("Couldn't open archive"))
|
|
end
|
|
arclist[#arclist+1] = arc
|
|
if pathtab[i] == nil then
|
|
return arc -- this is the end of the path! Done drilling!
|
|
end
|
|
return drill_for_archive(arc, rebuild_path(pathtab, i), arclist)
|
|
end
|
|
end
|
|
ent = MojoSetup.archive.enumnext(archive)
|
|
end
|
|
|
|
MojoSetup.fatal(_("Archive not found"))
|
|
end
|
|
|
|
|
|
local function install_file(dest, perms, writefn, desc, manifestkey)
|
|
-- Upvalued so we don't look these up each time...
|
|
local fname = string.gsub(dest, "^.*/", "", 1) -- chop the dirs off...
|
|
local ptype = _("Installing")
|
|
local component = desc
|
|
local keepgoing = true
|
|
local callback = function(ticks, justwrote, bw, total)
|
|
local percent = -1
|
|
local item = fname
|
|
if total >= 0 then
|
|
MojoSetup.written = MojoSetup.written + justwrote
|
|
percent = calc_percent(MojoSetup.written, MojoSetup.totalwrite)
|
|
item = MojoSetup.format(_("%0: %1%%"), fname, calc_percent(bw, total))
|
|
end
|
|
keepgoing = MojoSetup.gui.progress(ptype, component, percent, item, true)
|
|
return keepgoing
|
|
end
|
|
|
|
-- !!! FIXME: maybe keep a separate list, so we can rollback installs
|
|
-- !!! FIXME: that are building on previous installations?
|
|
-- Add to manifest first, so we can delete it during rollback if i/o fails.
|
|
-- !!! FIXME: perms may be nil...we need a MojoSetup.defaultPermsString()...
|
|
manifest_add(MojoSetup.manifest, dest, manifestkey, "file", perms, nil, nil)
|
|
|
|
MojoSetup.gui.progressitem()
|
|
local written, sums = writefn(callback)
|
|
if not written then
|
|
if not keepgoing then
|
|
MojoSetup.logerror("User cancelled install during file write.")
|
|
MojoSetup.fatal()
|
|
else
|
|
MojoSetup.logerror("Failed to create file '" .. dest .. "'")
|
|
MojoSetup.fatal(_("File creation failed!"))
|
|
end
|
|
end
|
|
|
|
-- Readd it to the manifest, now with a checksum!
|
|
if manifestkey ~= nil then
|
|
manifest_delete(MojoSetup.manifest, dest)
|
|
manifest_add(MojoSetup.manifest, dest, manifestkey, "file", perms, sums, nil)
|
|
end
|
|
|
|
MojoSetup.loginfo("Created file '" .. dest .. "'")
|
|
MojoSetup.incrementgarbagecount()
|
|
end
|
|
|
|
|
|
local function install_file_from_archive(dest, archive, perms, desc, manifestkey)
|
|
local fn = function(callback)
|
|
return MojoSetup.writefile(archive, dest, perms, nil, callback)
|
|
end
|
|
return install_file(dest, perms, fn, desc, manifestkey)
|
|
end
|
|
|
|
|
|
local function install_file_from_stringtable(dest, t, perms, desc, manifestkey)
|
|
local fn = function(callback)
|
|
return MojoSetup.stringtabletofile(t, dest, perms, nil, callback)
|
|
end
|
|
return install_file(dest, perms, fn, desc, manifestkey)
|
|
end
|
|
|
|
|
|
local function install_file_from_string(dest, str, perms, desc, manifestkey)
|
|
local fn = function(callback)
|
|
return MojoSetup.stringtofile(str, dest, perms, nil, callback)
|
|
end
|
|
return install_file(dest, perms, fn, desc, manifestkey)
|
|
end
|
|
|
|
|
|
local function install_file_from_filesystem(dest, src, perms, desc, manifestkey, maxbytes)
|
|
local fn = function(callback)
|
|
return MojoSetup.copyfile(src, dest, perms, maxbytes, callback)
|
|
end
|
|
return install_file(dest, perms, fn, desc, manifestkey)
|
|
end
|
|
|
|
|
|
-- !!! FIXME: we should probably pump the GUI queue here, in case there are
|
|
-- !!! FIXME: thousands of symlinks in a row or something.
|
|
local function install_symlink(dest, lndest, manifestkey)
|
|
if not MojoSetup.platform.symlink(dest, lndest) then
|
|
MojoSetup.logerror("Failed to create symlink '" .. dest .. "'")
|
|
MojoSetup.fatal(_("Symlink creation failed!"))
|
|
end
|
|
|
|
manifest_add(MojoSetup.manifest, dest, manifestkey, "symlink", nil, nil, lndest)
|
|
MojoSetup.loginfo("Created symlink '" .. dest .. "' -> '" .. lndest .. "'")
|
|
end
|
|
|
|
|
|
-- !!! FIXME: we should probably pump the GUI queue here, in case there are
|
|
-- !!! FIXME: thousands of dirs in a row or something.
|
|
local function install_directory(dest, perms, manifestkey)
|
|
-- Chop any '/' chars from the end of the string...
|
|
dest = string.gsub(dest, "/+$", "")
|
|
|
|
if not MojoSetup.platform.mkdir(dest, perms) then
|
|
MojoSetup.logerror("Failed to create dir '" .. dest .. "'")
|
|
MojoSetup.fatal(_("Directory creation failed"))
|
|
end
|
|
|
|
manifest_add(MojoSetup.manifest, dest, manifestkey, "directory", perms, nil, nil)
|
|
MojoSetup.loginfo("Created directory '" .. dest .. "'")
|
|
end
|
|
|
|
|
|
local function install_parent_dirs(path, manifestkey)
|
|
-- Chop any '/' chars from the end of the string...
|
|
path = string.gsub(path, "/+$", "")
|
|
|
|
-- Build each piece of final path. The gmatch() skips the last element.
|
|
local fullpath = ""
|
|
for item in string.gmatch(path, "(.-)/") do
|
|
if item ~= "" then
|
|
fullpath = fullpath .. "/" .. item
|
|
if not MojoSetup.platform.exists(fullpath) then
|
|
install_directory(fullpath, nil, manifestkey)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function permit_write(dest, entinfo, file)
|
|
local allowoverwrite = true
|
|
if MojoSetup.platform.exists(dest) then
|
|
-- never "permit" existing dirs, so they don't rollback.
|
|
if entinfo.type == "dir" then
|
|
allowoverwrite = false
|
|
else
|
|
if MojoSetup.forceoverwrite ~= nil then
|
|
allowoverwrite = MojoSetup.forceoverwrite
|
|
else
|
|
-- !!! FIXME: option/package-wide overwrite?
|
|
allowoverwrite = file.allowoverwrite
|
|
if not allowoverwrite then
|
|
MojoSetup.loginfo("File '" .. dest .. "' already exists.")
|
|
local text = MojoSetup.format(_("File '%0' already exists! Replace?"), dest)
|
|
local ynan = MojoSetup.promptynan(_("Conflict!"), text, true)
|
|
if ynan == "always" then
|
|
MojoSetup.forceoverwrite = true
|
|
allowoverwrite = true
|
|
elseif ynan == "never" then
|
|
MojoSetup.forceoverwrite = false
|
|
allowoverwrite = false
|
|
elseif ynan == "yes" then
|
|
allowoverwrite = true
|
|
elseif ynan == "no" then
|
|
allowoverwrite = false
|
|
end
|
|
end
|
|
end
|
|
|
|
-- !!! FIXME: Setup.File.mustoverwrite to override "never"?
|
|
|
|
if allowoverwrite then
|
|
local id = #MojoSetup.rollbacks + 1
|
|
local f = MojoSetup.rollbackdir .. "/" .. id
|
|
install_parent_dirs(f, MojoSetup.metadatakey)
|
|
MojoSetup.rollbacks[id] = dest
|
|
if not MojoSetup.movefile(dest, f) then
|
|
MojoSetup.fatal(_("Couldn't backup file for rollback"))
|
|
end
|
|
MojoSetup.loginfo("Moved rollback #" .. id .. ": '" .. dest .. "' -> '" .. f .. "'")
|
|
|
|
-- Make sure this isn't already in the manifest...
|
|
if MojoSetup.manifest[dest] ~= nil then
|
|
manifest_delete(MojoSetup.manifest, dest)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return allowoverwrite
|
|
end
|
|
|
|
|
|
local function install_archive_entity(dest, ent, archive, desc, manifestkey, perms)
|
|
install_parent_dirs(dest, manifestkey)
|
|
if ent.type == "file" then
|
|
install_file_from_archive(dest, archive, perms, desc, manifestkey)
|
|
elseif ent.type == "dir" then
|
|
install_directory(dest, perms, manifestkey)
|
|
elseif ent.type == "symlink" then
|
|
install_symlink(dest, ent.linkdest, manifestkey)
|
|
else -- !!! FIXME: device nodes, etc...
|
|
-- !!! FIXME: should this be fatal?
|
|
MojoSetup.fatal(_("Unknown file type in archive"))
|
|
end
|
|
end
|
|
|
|
|
|
local function install_archive_entry(archive, ent, file, option)
|
|
local entdest = ent.filename
|
|
if entdest == nil then return end -- probably can't happen...
|
|
|
|
-- Set destination in native filesystem. May be default or explicit.
|
|
local dest = file.destination
|
|
if dest == nil then
|
|
dest = entdest
|
|
else
|
|
dest = dest .. "/" .. entdest
|
|
end
|
|
|
|
local perms = file.permissions -- may be nil
|
|
|
|
if file.filter ~= nil then
|
|
local filterperms
|
|
dest, filterperms = file.filter(dest)
|
|
if filterperms ~= nil then
|
|
perms = filterperms
|
|
end
|
|
end
|
|
|
|
if dest ~= nil then -- Only install if file wasn't filtered out
|
|
dest = MojoSetup.destination .. "/" .. dest
|
|
if permit_write(dest, ent, file) then
|
|
local desc = option.description
|
|
install_archive_entity(dest, ent, archive, desc, desc, perms)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function install_archive(archive, file, option, dataprefix)
|
|
if not MojoSetup.archive.enumerate(archive) then
|
|
MojoSetup.fatal(_("Couldn't enumerate archive"))
|
|
end
|
|
|
|
local isbase = (archive == MojoSetup.archive.base)
|
|
local single_match = true
|
|
local wildcards = file.wildcards
|
|
|
|
-- If there's only one explicit file we're looking for, we don't have to
|
|
-- iterate the whole archive...we can stop as soon as we find it.
|
|
if wildcards == nil then
|
|
single_match = false
|
|
else
|
|
if type(wildcards) == "string" then
|
|
wildcards = { wildcards }
|
|
end
|
|
if #wildcards > 1 then
|
|
single_match = false
|
|
else
|
|
for i,v in ipairs(wildcards) do
|
|
if string.find(v, "[*?]") ~= nil then
|
|
single_match = false
|
|
break -- no reason to keep iterating...
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local ent = MojoSetup.archive.enumnext(archive)
|
|
while ent ~= nil do
|
|
-- If inside GBaseArchive (no URL lead in string), then we
|
|
-- want to clip to data/ directory...
|
|
if isbase and (string.len(dataprefix) > 0) then
|
|
local count
|
|
ent.filename, count = string.gsub(ent.filename, "^" .. dataprefix, "", 1)
|
|
if count == 0 then
|
|
ent.filename = nil
|
|
end
|
|
end
|
|
|
|
-- See if we should install this file...
|
|
if (ent.filename ~= nil) and (ent.filename ~= "") then
|
|
local should_install = false
|
|
if wildcards == nil then
|
|
should_install = true
|
|
else
|
|
for i,v in ipairs(wildcards) do
|
|
if MojoSetup.wildcardmatch(ent.filename, v) then
|
|
should_install = true
|
|
break -- no reason to keep iterating...
|
|
end
|
|
end
|
|
end
|
|
|
|
if should_install then
|
|
install_archive_entry(archive, ent, file, option)
|
|
if single_match then
|
|
break -- no sense in iterating further if we're done.
|
|
end
|
|
end
|
|
end
|
|
|
|
-- and check the next entry in the archive...
|
|
ent = MojoSetup.archive.enumnext(archive)
|
|
end
|
|
end
|
|
|
|
|
|
local function install_basepath(basepath, file, option, dataprefix)
|
|
-- Obviously, we don't want to enumerate the entire physical filesystem,
|
|
-- so we'll dig through each path element with MojoPlatform_exists()
|
|
-- until we find one that doesn't, then we'll back up and try to open
|
|
-- that as a directory, and then a file archive, and start drilling from
|
|
-- there. Fun.
|
|
|
|
local function create_basepath_archive(path)
|
|
local archive = MojoSetup.archive.fromdir(path)
|
|
if archive == nil then
|
|
archive = MojoSetup.archive.fromfile(path)
|
|
if archive == nil then
|
|
MojoSetup.fatal(_("Couldn't open archive"))
|
|
end
|
|
end
|
|
return archive
|
|
end
|
|
|
|
-- fast path: See if the whole path exists. This is probably the normal
|
|
-- case, but it won't work for archives-in-archives.
|
|
if MojoSetup.platform.exists(basepath) then
|
|
local archive = create_basepath_archive(basepath)
|
|
install_archive(archive, file, option, dataprefix)
|
|
MojoSetup.archive.close(archive)
|
|
else
|
|
-- Check for archives-in-archives...
|
|
local path = ""
|
|
local paths = split_path(basepath)
|
|
for i,v in ipairs(paths) do
|
|
local knowngood = path
|
|
path = path .. "/" .. v
|
|
if not MojoSetup.platform.exists(path) then
|
|
if knowngood == "" then
|
|
MojoSetup.fatal(_("Archive not found"))
|
|
end
|
|
local archive = create_basepath_archive(knowngood)
|
|
local arclist = { archive }
|
|
path = rebuild_path(paths, i)
|
|
local arc = drill_for_archive(archive, path, arclist)
|
|
install_archive(arc, file, option, dataprefix)
|
|
close_archive_list(arclist)
|
|
return -- we're done here
|
|
end
|
|
end
|
|
|
|
-- wait, the whole thing exists now? Did this just move in?
|
|
install_basepath(basepath, file, option, dataprefix) -- try again, I guess...
|
|
end
|
|
end
|
|
|
|
|
|
local function set_destination(dest)
|
|
-- Chop any '/' chars from the end of the string...
|
|
dest = string.gsub(dest, "/+$", "")
|
|
|
|
MojoSetup.loginfo("Install dest: '" .. dest .. "'")
|
|
MojoSetup.destination = dest
|
|
MojoSetup.metadatadir = MojoSetup.destination .. "/" .. MojoSetup.metadatadirname
|
|
MojoSetup.controldir = MojoSetup.metadatadir -- .. "/control"
|
|
MojoSetup.manifestdir = MojoSetup.metadatadir .. "/manifest"
|
|
MojoSetup.scratchdir = MojoSetup.metadatadir .. "/tmp"
|
|
MojoSetup.rollbackdir = MojoSetup.scratchdir .. "/rollbacks"
|
|
MojoSetup.downloaddir = MojoSetup.scratchdir .. "/downloads"
|
|
end
|
|
|
|
|
|
local function run_config_defined_hook(func, pkg)
|
|
if func ~= nil then
|
|
local errstr = func(pkg)
|
|
if errstr ~= nil then
|
|
MojoSetup.fatal(errstr)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- The XML manifest is compatible with the loki_setup manifest schema, since
|
|
-- it has a reasonable set of data, and allows you to use loki_update or
|
|
-- loki_patch with a MojoSetup installation. Please note that we never ever
|
|
-- look at this data! You are responsible for updating the other files if
|
|
-- you think it'll be important. The Unix MojoSetup uninstaller uses the
|
|
-- lua manifest, for example (but loki_uninstall can use the xml one,
|
|
-- so if you want, you can just drop in MojoSetup to replace loki_setup and
|
|
-- use the Loki tools for everything else.
|
|
local function build_xml_manifest(package)
|
|
local retval = {};
|
|
|
|
local function addstr(str)
|
|
retval[#retval+1] = str
|
|
end
|
|
|
|
addstr('<?xml version="1.0" encoding="UTF-8"?>\n')
|
|
addstr('<product name="')
|
|
addstr(package.id)
|
|
addstr('" desc="')
|
|
addstr(package.description)
|
|
addstr('" xmlversion="1.6" root="')
|
|
addstr(package.root)
|
|
addstr('" ')
|
|
if package.update_url ~= nil then
|
|
addstr('update_url="')
|
|
addstr(package.update_url)
|
|
addstr('"')
|
|
end
|
|
addstr('>\n')
|
|
addstr('\t<component name="Default" version="')
|
|
addstr(package.version)
|
|
addstr('" default="yes">\n')
|
|
|
|
-- Need to group these by options.
|
|
local grouped = {}
|
|
for fname,entity in pairs(package.manifest) do
|
|
local key = entity.key
|
|
if grouped[key] == nil then
|
|
grouped[key] = {}
|
|
end
|
|
entity.path = fname
|
|
local list = grouped[key]
|
|
list[#list+1] = entity
|
|
end
|
|
|
|
for desc,items in pairs(grouped) do
|
|
addstr('\t\t<option name="')
|
|
addstr(desc)
|
|
addstr('">\n')
|
|
for i,item in ipairs(items) do
|
|
local type = item.type
|
|
if type == "dir" then
|
|
type = "directory" -- loki_setup expects this string.
|
|
end
|
|
|
|
-- !!! FIXME: files from archives aren't filling item.mode in
|
|
-- !!! FIXME: because it figures out the perms from the archive's
|
|
-- !!! FIXME: C struct in native code. Need to get that into Lua.
|
|
-- !!! FIXME: (and get the perms uint16/string conversion cleaned up)
|
|
local mode = item.mode
|
|
if mode == nil then
|
|
mode = "0644" -- !!! FIXME
|
|
end
|
|
|
|
local path = item.path
|
|
item.path = nil -- we added this when grouping. Remove it now.
|
|
|
|
addstr('\t\t\t<')
|
|
addstr(type)
|
|
|
|
if type == "file" then
|
|
if item.checksums ~= nil then
|
|
for k,v in pairs(item.checksums) do
|
|
addstr(' ')
|
|
addstr(k)
|
|
addstr('="')
|
|
addstr(v)
|
|
addstr('"')
|
|
end
|
|
end
|
|
addstr(' mode="')
|
|
addstr(mode)
|
|
addstr('"')
|
|
elseif type == "directory" then
|
|
addstr(' mode="')
|
|
addstr(mode)
|
|
addstr('"')
|
|
elseif type == "symlink" then
|
|
addstr(' dest="')
|
|
addstr(item.linkdest)
|
|
addstr('" mode="0777"')
|
|
end
|
|
|
|
addstr('>')
|
|
addstr(path)
|
|
addstr('</')
|
|
addstr(type)
|
|
addstr('>\n')
|
|
end
|
|
addstr('\t\t</option>\n');
|
|
end
|
|
|
|
addstr('\t</component>\n</product>\n\n')
|
|
|
|
return retval
|
|
end
|
|
|
|
|
|
local function serialize(obj, prefix, postfix)
|
|
local retval = {}
|
|
local function addstr(str)
|
|
retval[#retval+1] = str
|
|
end
|
|
|
|
if prefix ~= nil then
|
|
addstr(prefix)
|
|
end
|
|
|
|
local function _serialize(obj, indent)
|
|
local objtype = type(obj)
|
|
if objtype == "nil" then
|
|
addstr("nil")
|
|
elseif (objtype == "number") or (objtype == "boolean") then
|
|
addstr(tostring(obj))
|
|
elseif objtype == "string" then
|
|
addstr(string.format("%q", obj))
|
|
elseif objtype == "function" then
|
|
addstr("load(")
|
|
addstr(string.format("%q", string.dump(obj)))
|
|
addstr(")")
|
|
elseif objtype == "table" then
|
|
addstr("{\n")
|
|
local tab = string.rep("\t", indent)
|
|
for k,v in pairs(obj) do
|
|
local key = k
|
|
addstr(tab)
|
|
if type(key) == "number" then
|
|
addstr('[')
|
|
addstr(key)
|
|
addstr(']')
|
|
elseif not string.match(key, "^[_a-zA-Z][_a-zA-Z0-9]*$") then
|
|
addstr('[')
|
|
addstr(string.format("%q", key))
|
|
addstr(']')
|
|
else
|
|
addstr(key)
|
|
end
|
|
addstr(" = ")
|
|
_serialize(v, indent+1)
|
|
addstr(",\n")
|
|
end
|
|
addstr(string.rep("\t", indent-1))
|
|
addstr("}")
|
|
else
|
|
MojoSetup.logerror("unexpected object to serialize (" ..
|
|
objtype .. "): '" .. tostring(obj) .. "'")
|
|
MojoSetup.fatal(_("BUG: Unhandled data type"))
|
|
end
|
|
end
|
|
|
|
_serialize(obj, 1)
|
|
|
|
if postfix ~= nil then
|
|
addstr(postfix)
|
|
end
|
|
|
|
return retval
|
|
end
|
|
|
|
|
|
local function build_lua_manifest(package)
|
|
return serialize(package, 'MojoSetup.package = ', '\n\n')
|
|
end
|
|
|
|
|
|
local function build_txt_manifest(package)
|
|
local flat = flatten_manifest(package.manifest)
|
|
local retval = {}
|
|
for i,v in pairs(flat) do
|
|
retval[#retval+1] = v
|
|
retval[#retval+1] = "\n"
|
|
end
|
|
return retval
|
|
end
|
|
|
|
|
|
local function install_control_app(desc, key)
|
|
local dst, src
|
|
|
|
-- We copy the installer binary itself, and any auxillary files it needs,
|
|
-- like this Lua script, to a metadata directory in the installation.
|
|
-- Unfortunately, the binary might be a self-extracting installer that
|
|
-- has gigabytes of now-unnecessary data appended to it, so we need to
|
|
-- decide if that's the case and, if so, extract just the program itself
|
|
-- from the start of the file.
|
|
local maxbytes = -1 -- copy whole thing by default.
|
|
local base = MojoSetup.archive.base
|
|
|
|
dst = MojoSetup.controldir .. "/" .. MojoSetup.controlappname
|
|
src = MojoSetup.info.binarypath
|
|
if src == MojoSetup.info.basearchivepath then
|
|
maxbytes = MojoSetup.archive.offsetofstart(base)
|
|
if maxbytes <= 0 then
|
|
MojoSetup.fatal(_("BUG: Unexpected value"))
|
|
end
|
|
end
|
|
|
|
-- don't overwrite preexisting stuff.
|
|
if not MojoSetup.platform.exists(dst) then
|
|
local perms = "0755" -- !!! FIXME
|
|
install_parent_dirs(dst, key)
|
|
install_file_from_filesystem(dst, src, perms, desc, key, maxbytes)
|
|
end
|
|
|
|
-- Okay, now we need all the support files.
|
|
if not MojoSetup.archive.enumerate(base) then
|
|
MojoSetup.fatal(_("Couldn't enumerate archive"))
|
|
end
|
|
|
|
local needdirs = { "scripts", "guis", "meta" }
|
|
|
|
local ent = MojoSetup.archive.enumnext(base)
|
|
while ent ~= nil do
|
|
-- Make sure this is in a directory we want to write out...
|
|
local should_write = false
|
|
|
|
if (ent.filename ~= nil) and (ent.filename ~= "") then
|
|
for i,dir in ipairs(needdirs) do
|
|
local clipdir = "^" .. dir .. "/"
|
|
if string.find(ent.filename, clipdir) ~= nil then
|
|
should_write = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if should_write then
|
|
dst = MojoSetup.controldir .. "/" .. ent.filename
|
|
-- don't overwrite preexisting stuff.
|
|
if not MojoSetup.platform.exists(dst) then
|
|
install_archive_entity(dst, ent, base, desc, key, perms)
|
|
end
|
|
end
|
|
|
|
-- and check the next entry in the archive...
|
|
ent = MojoSetup.archive.enumnext(base)
|
|
end
|
|
|
|
-- okay, we're written out.
|
|
end
|
|
|
|
local function shquote_string(str)
|
|
-- Quote the string so it can safely be passed to the shell
|
|
str = string.gsub(str, "\\", "\\\\")
|
|
str = string.gsub(str, "[$]", "\\$")
|
|
str = string.gsub(str, "\"", "\\\"")
|
|
str = string.gsub(str, "`", "\\`")
|
|
return "\"" ..str.. "\""
|
|
end
|
|
|
|
local function install_unix_uninstaller(desc, key)
|
|
-- Write a script out that calls the uninstaller.
|
|
local fname = MojoSetup.destination .. "/" ..
|
|
"uninstall-" .. MojoSetup.install.id .. ".sh"
|
|
|
|
-- Man, I hate escaping shell strings...
|
|
local bin = "\"`dirname \\\"$0\\\"`\"/" ..
|
|
shquote_string(MojoSetup.metadatadirname .. "/" .. MojoSetup.controlappname)
|
|
|
|
local script =
|
|
"#!/bin/sh\n" ..
|
|
"exec " .. bin .. " uninstall " .. shquote_string(MojoSetup.install.id) .. " \"$@\"\n"
|
|
|
|
install_parent_dirs(fname, key)
|
|
install_file_from_string(fname, script, "0755", desc, key)
|
|
end
|
|
|
|
|
|
local function install_product_keys(productkeys)
|
|
for desc,prodkey in pairs(productkeys) do
|
|
local dest = MojoSetup.destination .. "/" .. prodkey.destination
|
|
local productkey = prodkey.productkey
|
|
local component = prodkey.component
|
|
-- !!! FIXME: Windows registry support.
|
|
-- !!! FIXME: file permissions for product keys?
|
|
install_parent_dirs(dest, component)
|
|
install_file_from_string(dest, productkey, "0644", desc, component)
|
|
end
|
|
end
|
|
|
|
|
|
local function install_manifests(desc, key)
|
|
-- We write out a Lua script as a data definition language, a
|
|
-- loki_setup-compatible XML manifest, and a straight text file of
|
|
-- all the filenames. Take your pick.
|
|
|
|
local perms = "0644" -- !!! FIXME
|
|
local basefname = MojoSetup.manifestdir .. "/" .. MojoSetup.install.id
|
|
local lua_fname = basefname .. ".lua"
|
|
local xml_fname = basefname .. ".xml"
|
|
local txt_fname = basefname .. ".txt"
|
|
|
|
-- We have to cheat and just plug these into the manifest directly, since
|
|
-- they won't show up until after we write them out, otherwise.
|
|
-- Obviously, we don't have checksums for them, either, as we haven't
|
|
-- assembled the data yet!
|
|
|
|
manifest_add(MojoSetup.manifest, lua_fname, key, "file", perms, nil, nil)
|
|
manifest_add(MojoSetup.manifest, xml_fname, key, "file", perms, nil, nil)
|
|
manifest_add(MojoSetup.manifest, txt_fname, key, "file", perms, nil, nil)
|
|
|
|
-- build the "package" table that we serialize, etc.
|
|
local package =
|
|
{
|
|
id = MojoSetup.install.id,
|
|
vendor = MojoSetup.install.vendor,
|
|
description = MojoSetup.install.description,
|
|
root = MojoSetup.destination,
|
|
update_url = MojoSetup.install.updateurl,
|
|
version = MojoSetup.install.version,
|
|
manifest = MojoSetup.manifest,
|
|
splash = MojoSetup.install.splash,
|
|
splashpos = MojoSetup.install.splashpos,
|
|
desktopmenuitems = MojoSetup.install.desktopmenuitems,
|
|
preuninstall = MojoSetup.install.preuninstall,
|
|
postuninstall = MojoSetup.install.postuninstall
|
|
}
|
|
|
|
-- now build these things...
|
|
install_parent_dirs(lua_fname, key)
|
|
install_file_from_stringtable(lua_fname, build_lua_manifest(package), perms, desc, nil)
|
|
install_file_from_stringtable(xml_fname, build_xml_manifest(package), perms, desc, nil)
|
|
install_file_from_stringtable(txt_fname, build_txt_manifest(package), perms, desc, nil)
|
|
end
|
|
|
|
|
|
local function freedesktop_menuitem_filename(pkg, idx) -- only for Unix.
|
|
local vendor = string.gsub(pkg.vendor, "%.", "_")
|
|
local fname = vendor .. "-" .. pkg.id .. "_" .. idx .. ".desktop"
|
|
return MojoSetup.metadatadir .. "/" .. fname
|
|
end
|
|
|
|
|
|
local function uninstall_desktop_menu_items(pkg)
|
|
-- !!! FIXME: GUI progress?
|
|
if pkg.desktopmenuitems ~= nil then
|
|
for i,v in ipairs(pkg.desktopmenuitems) do
|
|
if MojoSetup.info.platform == "windows" then
|
|
MojoSetup.fatal(_("Unimplemented")) -- !!! FIXME: write me.
|
|
elseif MojoSetup.info.platform == "macosx" then
|
|
MojoSetup.fatal(_("Unimplemented")) -- !!! FIXME: write me.
|
|
elseif MojoSetup.info.platform == "beos" then
|
|
MojoSetup.fatal(_("Unimplemented")) -- !!! FIXME: write me.
|
|
else -- freedesktop, we hope.
|
|
local fname = freedesktop_menuitem_filename(pkg, i)
|
|
if not MojoSetup.platform.uninstalldesktopmenuitem(fname) then
|
|
MojoSetup.fatal(_("Failed to uninstall desktop menu item"))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function install_freedesktop_menuitem(pkg, idx, item) -- only for Unix.
|
|
local icon
|
|
local dest = MojoSetup.destination
|
|
if item.builtin_icon then
|
|
icon = item.icon
|
|
else
|
|
icon = dest .. "/" .. item.icon
|
|
end
|
|
|
|
local cmdline = MojoSetup.format(item.commandline, dest)
|
|
|
|
-- Try to escape some characters...
|
|
cmdline = '"' .. string.gsub(string.gsub(cmdline, "\"","\\\""), "%%", "%%%%") .. '"'
|
|
|
|
local t = { "[Desktop Entry]\n" }
|
|
local function addpair(key, val)
|
|
t[#t+1] = key
|
|
t[#t+1] = '='
|
|
t[#t+1] = val
|
|
t[#t+1] = '\n'
|
|
end
|
|
|
|
addpair("Encoding", "UTF-8")
|
|
addpair("Value", "1.0")
|
|
addpair("Type", "Application")
|
|
addpair("Name", item.name)
|
|
addpair("GenericName", item.genericname)
|
|
addpair("Comment", item.tooltip)
|
|
addpair("Icon", icon)
|
|
addpair("Exec", cmdline)
|
|
addpair("Categories", flatten_list(item.category))
|
|
|
|
if item.mimetype ~= nil then
|
|
addpair("MimeType", flatten_list(item.mimetype))
|
|
end
|
|
|
|
if item.workingdir ~= nil then
|
|
addpair("Path", MojoSetup.format(item.workingdir, dest))
|
|
end
|
|
|
|
t[#t+1] = '\n'
|
|
|
|
local fname = freedesktop_menuitem_filename(pkg, idx)
|
|
local perms = "0644" -- !!! FIXME
|
|
local key = MojoSetup.metadatakey
|
|
local desc = MojoSetup.metadatadesc
|
|
|
|
--MojoSetup.logdebug("Install FreeDesktop file")
|
|
--MojoSetup.logdebug(fname)
|
|
--MojoSetup.logdebug(str)
|
|
install_parent_dirs(fname, key)
|
|
install_file_from_stringtable(fname, t, perms, desc, key)
|
|
if not MojoSetup.platform.installdesktopmenuitem(fname) then
|
|
MojoSetup.fatal(_("Failed to install desktop menu item"))
|
|
end
|
|
end
|
|
|
|
|
|
local function install_desktop_menu_items(pkg)
|
|
-- !!! FIXME: GUI progress?
|
|
if pkg.desktopmenuitems ~= nil then
|
|
for i,item in ipairs(pkg.desktopmenuitems) do
|
|
if not item.disabled then
|
|
if MojoSetup.info.platform == "windows" then
|
|
MojoSetup.fatal(_("Unimplemented")) -- !!! FIXME: write me.
|
|
elseif MojoSetup.info.platform == "macosx" then
|
|
MojoSetup.fatal(_("Unimplemented")) -- !!! FIXME: write me.
|
|
elseif MojoSetup.info.platform == "beos" then
|
|
MojoSetup.fatal(_("Unimplemented")) -- !!! FIXME: write me.
|
|
else -- freedesktop, we hope.
|
|
install_freedesktop_menuitem(pkg, i, item)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function get_productkey(thisstage, maxstage, desc, fmt, verify, dest, manifestkey)
|
|
local key = nil
|
|
local userkey = nil
|
|
local retval = nil
|
|
|
|
-- Retrieve the previous entry, in case we're stepping back over a stage.
|
|
-- This lets the user edit it or jsut move forward without typing the
|
|
-- whole thing again.
|
|
if MojoSetup.productkeys[desc] ~= nil then
|
|
userkey = MojoSetup.productkeys[desc].user_productkey
|
|
end
|
|
|
|
while key == nil do
|
|
retval, userkey = MojoSetup.gui.productkey(desc, fmt, userkey, thisstage, maxstage)
|
|
if retval ~= 1 then
|
|
return retval -- user hit back or cancel.
|
|
end
|
|
|
|
key = userkey
|
|
if verify ~= nil then
|
|
local ok, newkey = verify(userkey)
|
|
if not ok then
|
|
MojoSetup.msgbox(
|
|
_("Invalid product key"),
|
|
_("That key appears to be invalid. Please try again."))
|
|
key = nil
|
|
elseif newkey ~= nil then
|
|
key = newkey
|
|
end
|
|
end
|
|
end
|
|
|
|
for desckey,prodkey in pairs(MojoSetup.productkeys) do
|
|
if (prodkey.destination == dest) and (desckey ~= desc) then
|
|
MojoSetup.logwarning("More than one product key with same destination!")
|
|
break
|
|
end
|
|
end
|
|
|
|
MojoSetup.productkeys[desc] = {
|
|
destination = dest,
|
|
productkey = key,
|
|
user_productkey = userkey,
|
|
component = manifestkey
|
|
}
|
|
|
|
return 1
|
|
end
|
|
|
|
|
|
local function start_gui(desc, splashfname, splashpos)
|
|
if splashfname ~= nil then
|
|
splashfname = 'meta/' .. splashfname
|
|
end
|
|
|
|
if not MojoSetup.gui.start(desc, splashfname, splashpos) then
|
|
MojoSetup.fatal(_("GUI failed to start"))
|
|
end
|
|
|
|
MojoSetup.gui_started = true
|
|
end
|
|
|
|
|
|
local function stop_gui()
|
|
MojoSetup.gui.stop()
|
|
MojoSetup.gui_started = nil
|
|
end
|
|
|
|
|
|
local function do_install(install)
|
|
MojoSetup.forceoverwrite = nil
|
|
MojoSetup.written = 0
|
|
MojoSetup.totalwrite = 0
|
|
MojoSetup.downloaded = 0
|
|
MojoSetup.totaldownload = 0
|
|
MojoSetup.install = install
|
|
MojoSetup.installed_menu_items = false
|
|
|
|
-- !!! FIXME: need a cmdline to automate cdkey entry?
|
|
local skipeulas = MojoSetup.cmdline("i-agree-to-all-licenses")
|
|
local skipreadmes = MojoSetup.cmdline("noreadme")
|
|
local skipoptions = MojoSetup.cmdline("nooptions")
|
|
local skipprompt = MojoSetup.cmdline("noprompt")
|
|
|
|
-- !!! FIXME: try to sanity check everything we can here
|
|
-- !!! FIXME: (unsupported URLs, bogus media IDs, etc.)
|
|
-- !!! FIXME: I would like everything possible to fail here instead of
|
|
-- !!! FIXME: when a user happens to pick an option no one tested...
|
|
|
|
if (install.options == nil) and (install.optiongroups == nil) then
|
|
MojoSetup.fatal(_("BUG: no options"))
|
|
end
|
|
|
|
-- The uninstaller support needs a manifest to know what to do. Force it on.
|
|
if (install.support_uninstall) and (not install.write_manifest) then
|
|
MojoSetup.fatal(_("BUG: support_uninstall requires write_manifest"))
|
|
end
|
|
|
|
-- Desktop icons should probably require uninstall so we don't clutter
|
|
-- the system with no option for reversal later.
|
|
-- !!! FIXME: will miss menu items that are Setup.Option children...
|
|
if (install.desktopmenuitems ~= nil) and (not install.support_uninstall) then
|
|
MojoSetup.fatal(_("BUG: Setup.DesktopMenuItem requires support_uninstall"))
|
|
end
|
|
|
|
-- Manifest support requires the Lua parser.
|
|
if (install.write_manifest) and (not MojoSetup.info.luaparser) then
|
|
MojoSetup.fatal(_("BUG: write_manifest requires Lua parser support"))
|
|
end
|
|
|
|
-- This is to save us the trouble of a loop every time we have to
|
|
-- find media by id...
|
|
MojoSetup.media = {}
|
|
if install.medias ~= nil then
|
|
for k,v in pairs(install.medias) do
|
|
if MojoSetup.media[v.id] ~= nil then
|
|
MojoSetup.fatal(_("BUG: duplicate media id"))
|
|
end
|
|
MojoSetup.media[v.id] = v
|
|
end
|
|
end
|
|
|
|
-- Build a bunch of functions into a linear array...this lets us move
|
|
-- back and forth between stages of the install with customized functions
|
|
-- for each bit that have their own unique params embedded as upvalues.
|
|
-- So if there are three EULAs to accept, we'll call three variations of
|
|
-- the EULA function with three different tables that appear as local
|
|
-- variables, and the driver that calls this function will be able to
|
|
-- skip back and forth based on user input. This is a cool Lua thing.
|
|
local stages = {}
|
|
|
|
-- First stage: Make sure installer can run. Always fails or steps forward.
|
|
-- !!! FIXME: you can step back onto this...need a way to run some stages
|
|
-- !!! FIXME: only once...
|
|
if install.precheck ~= nil then
|
|
stages[#stages+1] = function ()
|
|
run_config_defined_hook(install.precheck, install)
|
|
return 1
|
|
end
|
|
end
|
|
|
|
-- Next stage: accept all global EULAs. Rejection of any EULA is considered
|
|
-- fatal. These are global EULAs for the install, per-option EULAs come
|
|
-- later.
|
|
if (install.eulas ~= nil) and (not skipeulas) then
|
|
for k,eula in pairs(install.eulas) do
|
|
local desc = eula.description
|
|
local fname = install.dataprefix .. eula.source
|
|
local accept_needed = not eula.accept_not_needed
|
|
|
|
-- (desc) and (fname) become upvalues in this function.
|
|
stages[#stages+1] = function (thisstage, maxstage)
|
|
local retval = MojoSetup.gui.readme(desc,fname,thisstage,maxstage)
|
|
if retval == 1 then
|
|
if accept_needed then
|
|
if not MojoSetup.promptyn(desc, _("Accept this license?"), false) then
|
|
MojoSetup.fatal(_("You must accept the license before you may install"))
|
|
end
|
|
end
|
|
end
|
|
return retval
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Next stage: enter all global products keys. These are global keys
|
|
-- for the install, per-option keys come later.
|
|
MojoSetup.productkeys = {}
|
|
if install.productkeys ~= nil then
|
|
for k,prodkey in pairs(install.productkeys) do
|
|
-- (prodkey) becomes an upvalue in this function.
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
return get_productkey(thisstage, maxstage, prodkey.description,
|
|
prodkey.format, prodkey.verify,
|
|
prodkey.destination,
|
|
MojoSetup.metadatakey)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Next stage: show any READMEs.
|
|
if (install.readmes ~= nil) and (not skipreadmes) then
|
|
for k,readme in pairs(install.readmes) do
|
|
local desc = readme.description
|
|
-- !!! FIXME: pull from archive?
|
|
local fname = install.dataprefix .. readme.source
|
|
-- (desc) and (fname) become upvalues in this function.
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
return MojoSetup.gui.readme(desc, fname, thisstage, maxstage)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Next stage: let user choose install destination.
|
|
-- The config file can force a destination if it has a really good reason
|
|
-- (system drivers that have to go in a specific place, for example),
|
|
-- but really really shouldn't in 99% of the cases.
|
|
local destcmdline = MojoSetup.cmdlinestr("destination")
|
|
if install.destination ~= nil then
|
|
set_destination(install.destination)
|
|
elseif destcmdline ~= nil then
|
|
set_destination(destcmdline)
|
|
else
|
|
local recommend = nil
|
|
local recommended_cfg = install.recommended_destinations
|
|
if recommended_cfg ~= nil then
|
|
if type(recommended_cfg) == "string" then
|
|
recommended_cfg = { recommended_cfg }
|
|
end
|
|
|
|
recommend = {}
|
|
for i,v in ipairs(recommended_cfg) do
|
|
if MojoSetup.platform.isdir(v) then
|
|
if MojoSetup.platform.writable(v) then
|
|
recommend[#recommend+1] = v .. "/" .. install.id
|
|
end
|
|
end
|
|
end
|
|
|
|
if #recommend == 0 then
|
|
recommend = nil
|
|
end
|
|
end
|
|
|
|
-- (recommend) becomes an upvalue in this function.
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
local rc, dst
|
|
rc, dst = MojoSetup.gui.destination(recommend, thisstage, maxstage)
|
|
if rc == 1 then
|
|
set_destination(dst)
|
|
end
|
|
return rc
|
|
end
|
|
end
|
|
|
|
-- Next stage: let user choose install options.
|
|
-- This may not produce a GUI stage if it decides that all options
|
|
-- are either required or disabled.
|
|
if not skipoptions then -- (just take the defaults...)
|
|
-- (install) becomes an upvalue in this function.
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
-- This does some complex stuff with a hierarchy of tables in C.
|
|
return MojoSetup.gui.options(install, thisstage, maxstage)
|
|
end
|
|
end
|
|
|
|
|
|
-- Next stage: accept all option-specific EULAs.
|
|
-- Rejection of any EULA will put you back one stage (usually to the
|
|
-- install options), but if there is no previous stage, this becomes
|
|
-- fatal.
|
|
-- This may not produce a GUI stage if there are no selected options with
|
|
-- EULAs to accept.
|
|
if not skipeulas then
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
local option_eulas = {}
|
|
|
|
local function find_option_eulas(opts)
|
|
local options = opts['options']
|
|
if options ~= nil then
|
|
for k,v in pairs(options) do
|
|
if v.value then
|
|
if v.eulas ~= nil then
|
|
for ek,ev in pairs(v.eulas) do
|
|
option_eulas[#option_eulas+1] = ev
|
|
end
|
|
end
|
|
find_option_eulas(v)
|
|
end
|
|
end
|
|
end
|
|
|
|
local optiongroups = opts['optiongroups']
|
|
if optiongroups ~= nil then
|
|
for k,v in pairs(optiongroups) do
|
|
if not v.disabled then
|
|
find_option_eulas(v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
find_option_eulas(install)
|
|
|
|
for k,eula in pairs(option_eulas) do
|
|
local desc = eula.description
|
|
local fname = install.dataprefix .. eula.source
|
|
local accept_needed = not eula.accept_not_needed
|
|
local retval = MojoSetup.gui.readme(desc,fname,thisstage,maxstage)
|
|
if retval == 1 then
|
|
if accept_needed then
|
|
if not MojoSetup.promptyn(desc, _("Accept this license?"), false) then
|
|
-- can't go back? We have to die here instead.
|
|
if thisstage == 1 then
|
|
MojoSetup.fatal(_("You must accept the license before you may install"))
|
|
else
|
|
retval = -1 -- just step back a stage.
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if retval ~= 1 then
|
|
return retval
|
|
end
|
|
end
|
|
|
|
return 1 -- all licenses were agreed to. Go to the next stage.
|
|
end
|
|
end
|
|
|
|
-- Next stage: enter all option-specific product keys.
|
|
-- This may not produce a GUI stage if there are no selected options with
|
|
-- product keys. Many installers will use a single global key instead.
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
local options_with_keys = {}
|
|
|
|
local function find_options_with_keys(opts)
|
|
local options = opts['options']
|
|
if options ~= nil then
|
|
for k,v in pairs(options) do
|
|
if v.value then
|
|
if v.productkeys ~= nil then
|
|
options_with_keys[#options_with_keys+1] = v;
|
|
end
|
|
find_options_with_keys(v)
|
|
end
|
|
end
|
|
end
|
|
|
|
local optiongroups = opts['optiongroups']
|
|
if optiongroups ~= nil then
|
|
for k,v in pairs(optiongroups) do
|
|
if not v.disabled then
|
|
find_options_with_keys(v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
find_options_with_keys(install)
|
|
|
|
for optk,option in ipairs(options_with_keys) do
|
|
for k,prodkey in ipairs(option.productkeys) do
|
|
local retval = get_productkey(thisstage, maxstage,
|
|
prodkey.description,
|
|
prodkey.format, prodkey.verify,
|
|
prodkey.destination,
|
|
option.description)
|
|
if retval ~= 1 then
|
|
return retval
|
|
end
|
|
end
|
|
end
|
|
|
|
return 1 -- all product keys are entered. Go to the next stage.
|
|
end
|
|
|
|
-- Next stage: Make sure source list is sane.
|
|
-- This is not a GUI stage, it just needs to run between them.
|
|
-- This gets a little hairy.
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
-- Make sure we install the destination dir, so it's in the manifest.
|
|
if not MojoSetup.platform.exists(MojoSetup.destination) then
|
|
install_parent_dirs(MojoSetup.destination, MojoSetup.metadatakey)
|
|
install_directory(MojoSetup.destination, nil, MojoSetup.metadatakey)
|
|
end
|
|
|
|
local function process_file(option, file)
|
|
-- !!! FIXME: what happens if a file shows up in multiple options?
|
|
local src = file.source
|
|
local prot,host,path
|
|
if src ~= nil then -- no source, it's in GBaseArchive
|
|
prot,host,path = MojoSetup.spliturl(src)
|
|
end
|
|
if (src == nil) or (prot == "base://") then -- included content?
|
|
if MojoSetup.files.included == nil then
|
|
MojoSetup.files.included = {}
|
|
end
|
|
MojoSetup.files.included[file] = option
|
|
elseif prot == "media://" then
|
|
-- !!! FIXME: make sure media id is valid.
|
|
if MojoSetup.files.media == nil then
|
|
MojoSetup.files.media = {}
|
|
end
|
|
if MojoSetup.files.media[host] == nil then
|
|
MojoSetup.files.media[host] = {}
|
|
end
|
|
MojoSetup.files.media[host][file] = option
|
|
else
|
|
-- !!! FIXME: make sure we can handle this URL...
|
|
if MojoSetup.files.downloads == nil then
|
|
MojoSetup.files.downloads = {}
|
|
end
|
|
|
|
if option.bytes > 0 then
|
|
MojoSetup.totaldownload = MojoSetup.totaldownload + option.bytes
|
|
end
|
|
MojoSetup.files.downloads[file] = option
|
|
end
|
|
end
|
|
|
|
-- Sort an option into tables that say what sort of install it is.
|
|
-- This lets us batch all the things from one CD together,
|
|
-- do all the downloads first, etc.
|
|
local function process_option(option)
|
|
if option.bytes > 0 then
|
|
MojoSetup.totalwrite = MojoSetup.totalwrite + option.bytes
|
|
end
|
|
if option.files ~= nil then
|
|
for k,v in pairs(option.files) do
|
|
process_file(option, v)
|
|
end
|
|
end
|
|
|
|
if option.desktopmenuitems ~= nil then
|
|
for i,item in ipairs(option.desktopmenuitems) do
|
|
if install.desktopmenuitems == nil then
|
|
install.desktopmenuitems = {}
|
|
end
|
|
install.desktopmenuitems[#install.desktopmenuitems+1] = item
|
|
end
|
|
end
|
|
end
|
|
|
|
local function build_source_tables(opts)
|
|
local options = opts['options']
|
|
if options ~= nil then
|
|
for k,v in pairs(options) do
|
|
if v.value then
|
|
process_option(v)
|
|
build_source_tables(v)
|
|
end
|
|
end
|
|
end
|
|
|
|
local optiongroups = opts['optiongroups']
|
|
if optiongroups ~= nil then
|
|
for k,v in pairs(optiongroups) do
|
|
if not v.disabled then
|
|
build_source_tables(v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
run_config_defined_hook(install.preflight, install)
|
|
|
|
MojoSetup.files = {}
|
|
build_source_tables(install)
|
|
|
|
-- This dumps the files tables using MojoSetup.logdebug,
|
|
-- so it only spits out crap if debug-level logging is enabled.
|
|
MojoSetup.dumptable("MojoSetup.files", MojoSetup.files)
|
|
|
|
return 1 -- always go forward from non-GUI stage.
|
|
end
|
|
|
|
-- Next stage: Download external packages.
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
if MojoSetup.files.downloads ~= nil then
|
|
-- !!! FIXME: id will cause problems for download resume
|
|
-- !!! FIXME: id will chop filename extension
|
|
local id = 0
|
|
for file,option in pairs(MojoSetup.files.downloads) do
|
|
local f = MojoSetup.downloaddir .. "/" .. id
|
|
install_parent_dirs(f, MojoSetup.metadatakey)
|
|
id = id + 1
|
|
|
|
-- Upvalued so we don't look these up each time...
|
|
local url = file.source
|
|
local fname = string.gsub(url, "^.*/", "", 1) -- chop the dirs off...
|
|
local ptype = _("Downloading")
|
|
local component = option.description
|
|
local bps = 0
|
|
local bpsticks = 0
|
|
local ratestr = ''
|
|
local item = fname
|
|
local percent = -1
|
|
local callback = function(ticks, justwrote, bw, total)
|
|
if total < 0 then
|
|
-- adjust start point for d/l rate calculation...
|
|
bpsticks = ticks + 1000
|
|
else
|
|
if ticks >= bpsticks then
|
|
ratestr = make_rate_string(bps, bw, total)
|
|
bpsticks = ticks + 1000
|
|
bps = 0
|
|
end
|
|
bps = bps + justwrote
|
|
MojoSetup.downloaded = MojoSetup.downloaded + justwrote
|
|
percent = calc_percent(MojoSetup.downloaded,
|
|
MojoSetup.totaldownload)
|
|
|
|
item = MojoSetup.format(_("%0: %1%% (%2)"),
|
|
fname,
|
|
calc_percent(bw, total),
|
|
ratestr)
|
|
end
|
|
return MojoSetup.gui.progress(ptype, component, percent, item, true)
|
|
end
|
|
|
|
MojoSetup.loginfo("Download '" .. url .. "' to '" .. f .. "'")
|
|
MojoSetup.gui.progressitem()
|
|
local downloaded, sums = MojoSetup.download(url, f, nil, nil, callback)
|
|
if not downloaded then
|
|
MojoSetup.fatal(_("File download failed!"))
|
|
end
|
|
MojoSetup.downloads[#MojoSetup.downloads+1] = f
|
|
end
|
|
end
|
|
return 1
|
|
end
|
|
|
|
-- Next stage: actual installation.
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
run_config_defined_hook(install.preinstall)
|
|
|
|
-- Do stuff on media first, so the user finds out he's missing
|
|
-- disc 3 of 57 as soon as possible...
|
|
|
|
-- Since we sort all things to install by the media that contains
|
|
-- the source data, you should only have to insert each disc
|
|
-- once, no matter how they landed in the config file.
|
|
|
|
if MojoSetup.files.media ~= nil then
|
|
-- Build an array of media ids so we can sort them into a
|
|
-- reasonable order...disc 1 before disc 2, etc.
|
|
local medialist = {}
|
|
for mediaid,mediavalue in pairs(MojoSetup.files.media) do
|
|
medialist[#medialist+1] = mediaid
|
|
end
|
|
table.sort(medialist)
|
|
|
|
for mediaidx,mediaid in ipairs(medialist) do
|
|
local media = MojoSetup.media[mediaid]
|
|
local basepath = MojoSetup.findmedia(media.uniquefile)
|
|
while basepath == nil do
|
|
if not MojoSetup.gui.insertmedia(media.description) then
|
|
return 0 -- user cancelled.
|
|
end
|
|
basepath = MojoSetup.findmedia(media.uniquefile)
|
|
end
|
|
|
|
-- Media is ready, install everything from this one...
|
|
MojoSetup.loginfo("Found correct media at '" .. basepath .. "'")
|
|
local files = MojoSetup.files.media[mediaid]
|
|
for file,option in pairs(files) do
|
|
local prot,host,path = MojoSetup.spliturl(file.source)
|
|
install_basepath(basepath .. "/" .. path, file, option, install.dataprefix)
|
|
end
|
|
end
|
|
medialist = nil -- done with this.
|
|
end
|
|
|
|
if MojoSetup.files.downloads ~= nil then
|
|
local id = 0
|
|
for file,option in pairs(MojoSetup.files.downloads) do
|
|
local f = MojoSetup.downloaddir .. "/" .. id
|
|
id = id + 1
|
|
install_basepath(f, file, option, install.dataprefix)
|
|
end
|
|
end
|
|
|
|
if MojoSetup.files.included ~= nil then
|
|
for file,option in pairs(MojoSetup.files.included) do
|
|
local arc = MojoSetup.archive.base
|
|
if file.source == nil then
|
|
install_archive(arc, file, option, install.dataprefix)
|
|
else
|
|
local prot,host,path = MojoSetup.spliturl(file.source)
|
|
local arclist = {}
|
|
arc = drill_for_archive(arc, install.dataprefix .. path, arclist)
|
|
install_archive(arc, file, option, install.dataprefix)
|
|
close_archive_list(arclist)
|
|
end
|
|
end
|
|
end
|
|
|
|
if install.desktopmenuitems ~= nil then
|
|
install_desktop_menu_items(install)
|
|
MojoSetup.installed_menu_items = true
|
|
end
|
|
|
|
if install.support_uninstall then
|
|
if MojoSetup.info.platform == "windows" then
|
|
MojoSetup.fatal(_("Unimplemented")) -- !!! FIXME: write me.
|
|
else -- Unix, Mac OS X, BeOS...
|
|
install_unix_uninstaller(MojoSetup.metadatadesc, MojoSetup.metadatakey)
|
|
end
|
|
end
|
|
|
|
install_product_keys(MojoSetup.productkeys)
|
|
|
|
run_config_defined_hook(install.postinstall, install)
|
|
|
|
if install.write_manifest then
|
|
install_control_app(MojoSetup.metadatadesc, MojoSetup.metadatakey)
|
|
install_manifests(MojoSetup.metadatadesc, MojoSetup.metadatakey)
|
|
end
|
|
|
|
return 1 -- go to next stage.
|
|
end
|
|
|
|
-- Next stage: show results gui
|
|
if not skipprompt then
|
|
stages[#stages+1] = function(thisstage, maxstage)
|
|
MojoSetup.gui.final(_("Installation was successful."))
|
|
return 1 -- go forward.
|
|
end
|
|
end
|
|
|
|
-- Now make all this happen.
|
|
start_gui(install.description, install.splash, install.splashpos)
|
|
|
|
-- Make the stages available elsewhere.
|
|
MojoSetup.stages = stages
|
|
|
|
MojoSetup.manifest = {}
|
|
MojoSetup.rollbacks = {}
|
|
MojoSetup.downloads = {}
|
|
|
|
local i = 1
|
|
while MojoSetup.stages[i] ~= nil do
|
|
local stage = MojoSetup.stages[i]
|
|
local rc = stage(i, #MojoSetup.stages)
|
|
|
|
-- Too many times I forgot to return something. :)
|
|
if type(rc) ~= "number" then
|
|
MojoSetup.fatal(_("BUG: stage returned wrong type"))
|
|
end
|
|
|
|
if rc == 1 then
|
|
i = i + 1 -- next stage.
|
|
elseif rc == -1 then
|
|
if i == 1 then
|
|
MojoSetup.fatal(_("BUG: stepped back over start of stages"))
|
|
else
|
|
i = i - 1
|
|
end
|
|
elseif rc == 0 then
|
|
MojoSetup.fatal() -- user cancelled
|
|
else
|
|
MojoSetup.fatal(_("BUG: stage returned wrong value"))
|
|
end
|
|
end
|
|
|
|
-- Successful install, so delete conflicts we no longer need to rollback.
|
|
delete_rollbacks()
|
|
delete_files(MojoSetup.downloads)
|
|
delete_scratchdirs()
|
|
|
|
-- Don't let future errors delete files from successful installs...
|
|
MojoSetup.downloads = nil
|
|
MojoSetup.rollbacks = nil
|
|
|
|
stop_gui()
|
|
|
|
-- Done with these things. Make them eligible for garbage collection.
|
|
stages = nil
|
|
MojoSetup.manifest = nil
|
|
MojoSetup.manifestdir = nil
|
|
MojoSetup.metadatadir = nil
|
|
MojoSetup.controldir = nil
|
|
MojoSetup.scratchdir = nil
|
|
MojoSetup.rollbackdir = nil
|
|
MojoSetup.downloaddir = nil
|
|
MojoSetup.install = nil
|
|
MojoSetup.forceoverwrite = nil
|
|
MojoSetup.installed_menu_items = nil
|
|
MojoSetup.stages = nil
|
|
MojoSetup.files = nil
|
|
MojoSetup.productkeys = nil
|
|
MojoSetup.media = nil
|
|
MojoSetup.written = 0
|
|
MojoSetup.totalwrite = 0
|
|
MojoSetup.downloaded = 0
|
|
MojoSetup.totaldownload = 0
|
|
end
|
|
|
|
|
|
local function real_revertinstall()
|
|
if MojoSetup.gui_started then
|
|
MojoSetup.gui.final(_("Incomplete installation. We will revert any changes we made."))
|
|
end
|
|
|
|
MojoSetup.loginfo("Cleaning up half-finished installation...")
|
|
|
|
-- !!! FIXME: callbacks here.
|
|
if MojoSetup.installed_menu_items then
|
|
uninstall_desktop_menu_items(MojoSetup.install)
|
|
end
|
|
|
|
delete_files(MojoSetup.downloads)
|
|
delete_files(flatten_manifest(MojoSetup.manifest, prepend_dest_dir))
|
|
do_rollbacks()
|
|
delete_scratchdirs()
|
|
end
|
|
|
|
|
|
local function installer()
|
|
local postexec = nil
|
|
MojoSetup.loginfo("Installer starting")
|
|
|
|
MojoSetup.revertinstall = real_revertinstall -- replace the stub.
|
|
|
|
-- This dumps the table built from the user's config script using logdebug,
|
|
-- so it only spits out crap if debug-level logging is enabled.
|
|
MojoSetup.dumptable("MojoSetup.installs", MojoSetup.installs)
|
|
|
|
local saw_an_installer = false
|
|
for installkey,install in pairs(MojoSetup.installs) do
|
|
if not install.disabled then
|
|
saw_an_installer = true
|
|
do_install(install)
|
|
|
|
if (install.postexec ~= nil) then
|
|
postexec = string.gsub(install.postexec, "$DESTINATION", MojoSetup.destination)
|
|
end
|
|
|
|
MojoSetup.collectgarbage() -- nuke all the tables we threw around...
|
|
end
|
|
end
|
|
|
|
if not saw_an_installer then
|
|
MojoSetup.fatal(_("Nothing to do!"))
|
|
end
|
|
|
|
if postexec ~= nil then
|
|
MojoSetup.platform.exec(postexec)
|
|
end
|
|
|
|
MojoSetup.destination = nil
|
|
end
|
|
|
|
|
|
local function load_manifest(pkg)
|
|
local package = nil
|
|
if pkg == nil then
|
|
badcmdline()
|
|
end
|
|
|
|
MojoSetup.package = nil -- just in case.
|
|
|
|
-- We need to be in the metadata directory of an install or this fails.
|
|
local base = string.gsub(MojoSetup.info.basearchivepath, "/[^/]*$", "", 1)
|
|
set_destination(base) -- set up directories
|
|
if MojoSetup.metadatadir == MojoSetup.info.basearchivepath then
|
|
-- load the existing manifest first...
|
|
local ran = MojoSetup.runfilefromdir("manifest", pkg)
|
|
if (not ran) or (MojoSetup.package == nil) then
|
|
MojoSetup.fatal(MojoSetup.format(
|
|
_("Couldn't load manifest file for '%0'"), pkg))
|
|
end
|
|
|
|
-- Move this out of the global...
|
|
package = MojoSetup.package
|
|
MojoSetup.package = nil
|
|
end
|
|
|
|
-- note that we discard the base directory specified in the manifest, as
|
|
-- someone could have moved the installation's folder elsewhere. We'll
|
|
-- keep it up to date for loki_update or whatever to use, though.
|
|
package.destination = MojoSetup.destination
|
|
|
|
return package
|
|
end
|
|
|
|
|
|
local function manifest_management()
|
|
MojoSetup.loginfo("Manifest management starting")
|
|
local package = load_manifest(MojoSetup.info.argv[3])
|
|
|
|
local i = 4
|
|
while MojoSetup.info.argv[i] ~= nil do
|
|
local cmd = MojoSetup.info.argv[i]
|
|
i = i + 1
|
|
|
|
if cmd == "add" then
|
|
local key = MojoSetup.info.argv[i]
|
|
i = i + 1
|
|
local fname = MojoSetup.info.argv[i]
|
|
i = i + 1
|
|
if (key == nil) or (fname == nil) then
|
|
badcmdline()
|
|
end
|
|
|
|
MojoSetup.loginfo("Add '" ..fname.. "', '" ..key.. "' to manifest")
|
|
fname = MojoSetup.destination .. "/" .. fname
|
|
if not MojoSetup.platform.exists(fname) then
|
|
MojoSetup.fatal(MojoSetup.format(_("File %0 not found"), fname))
|
|
end
|
|
|
|
manifest_add(package.manifest, fname, key, nil, nil, nil, nil)
|
|
manifest_resync(package.manifest, fname, key)
|
|
|
|
elseif cmd == "delete" then
|
|
local fname = MojoSetup.info.argv[i]
|
|
i = i + 1
|
|
if fname == nil then
|
|
badcmdline()
|
|
end
|
|
MojoSetup.loginfo("Delete '" .. fname .. "' from manifest")
|
|
fname = MojoSetup.destination .. "/" .. fname
|
|
manifest_delete(package.manifest, fname)
|
|
|
|
elseif cmd == "resync" then
|
|
local fname = MojoSetup.info.argv[i]
|
|
i = i + 1
|
|
if fname == nil then
|
|
badcmdline()
|
|
end
|
|
MojoSetup.loginfo("Resync '" .. fname .. "' in manifest")
|
|
fname = MojoSetup.destination .. "/" .. fname
|
|
if not MojoSetup.platform.exists(fname) then
|
|
MojoSetup.fatal(MojoSetup.format(_("File %0 not found"), fname))
|
|
end
|
|
manifest_resync(package.manifest, fname)
|
|
|
|
elseif string.match(cmd, "^-") == nil then -- skip "-option" strings
|
|
MojoSetup.logerror("Unknown command '" .. cmd .. "'")
|
|
badcmdline()
|
|
end
|
|
end
|
|
|
|
-- !!! FIXME: duplication with install_manifests()
|
|
local perms = "0644" -- !!! FIXME
|
|
local basefname = MojoSetup.manifestdir .. "/" .. package.id
|
|
local lua_fname = basefname .. ".lua"
|
|
local xml_fname = basefname .. ".xml"
|
|
local txt_fname = basefname .. ".txt"
|
|
|
|
MojoSetup.loginfo("rebuilding manifests...")
|
|
|
|
-- !!! FIXME: rollback!
|
|
delete_files({lua_fname, xml_fname, txt_fname}, nil, false)
|
|
MojoSetup.stringtabletofile(build_lua_manifest(package), lua_fname, perms, nil, nil)
|
|
MojoSetup.stringtabletofile(build_xml_manifest(package), xml_fname, perms, nil, nil)
|
|
MojoSetup.stringtabletofile(build_txt_manifest(package), txt_fname, perms, nil, nil)
|
|
|
|
MojoSetup.loginfo("manifests rebuilt!")
|
|
end
|
|
|
|
|
|
local function uninstaller()
|
|
MojoSetup.loginfo("Uninstaller starting")
|
|
local package = load_manifest(MojoSetup.info.argv[3])
|
|
|
|
local title = _("Uninstall")
|
|
|
|
local uninstall_permitted = false
|
|
if MojoSetup.cmdline("force") then
|
|
uninstall_permitted = true
|
|
else
|
|
local question = _("Are you sure you want to uninstall '%0'?")
|
|
question = MojoSetup.format(question, package.description)
|
|
uninstall_permitted = MojoSetup.promptyn(title, question, false)
|
|
end
|
|
|
|
if uninstall_permitted then
|
|
start_gui(package.description, package.splash, package.splashpos)
|
|
run_config_defined_hook(package.preuninstall, package)
|
|
|
|
uninstall_desktop_menu_items(package)
|
|
|
|
-- Upvalued in callback so we don't look this up each time...
|
|
local ptype = _("Uninstalling")
|
|
local callback = function(fname, current, total)
|
|
fname = make_relative(fname, MojoSetup.destination)
|
|
local percent = 100 - calc_percent(current, total)
|
|
local component = package.manifest[fname].key
|
|
if component == nil then
|
|
component = ""
|
|
elseif component == MojoSetup.metadatakey then
|
|
component = MojoSetup.metadatadesc
|
|
end
|
|
|
|
local item = string.gsub(fname, "^.*/", "", 1) -- chop off dirs...
|
|
MojoSetup.gui.progressitem()
|
|
MojoSetup.gui.progress(ptype, component, percent, item, false)
|
|
return true -- !!! FIXME: need to disable cancel button in UI...
|
|
end
|
|
|
|
local filelist = flatten_manifest(package.manifest, prepend_dest_dir)
|
|
delete_files(filelist, callback, package.delete_error_is_fatal)
|
|
run_config_defined_hook(package.postuninstall, package)
|
|
if not MojoSetup.cmdline("noprompt") then
|
|
MojoSetup.gui.final(_("Uninstall complete"))
|
|
end
|
|
stop_gui()
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- Mainline...
|
|
|
|
local purpose = nil
|
|
|
|
-- Have to check argv instead of using cmdline(), for precision's sake.
|
|
-- Remember that unlike main()'s argv in C, Lua starts arrays at 1,
|
|
-- so argv[2] would be argv[1] in C. Fun.
|
|
local argv2 = MojoSetup.info.argv[2]
|
|
if argv2 == "manifest" then
|
|
purpose = manifest_management
|
|
elseif argv2 == "uninstall" then
|
|
purpose = uninstaller
|
|
else
|
|
purpose = installer
|
|
MojoSetup.runfile("config") -- This builds the MojoSetup.installs table.
|
|
end
|
|
|
|
-- We don't need the "Setup" namespace anymore. Make it eligible
|
|
-- for garbage collection.
|
|
Setup = nil
|
|
MojoSetup.collectgarbage() -- and collect it, etc.
|
|
|
|
-- Now do the real work...
|
|
purpose()
|
|
|
|
-- end of mojosetup_mainline.lua ...
|
|
|