--
--  remote_export.lua -- Monotone Lua extension command "mtn remote_export"
--  Copyright (C) 2009 Thomas Keller <me@thomaskeller.biz>
--
--  This program is made available under the GNU GPL version 2.0 or
--  greater. See the accompanying file COPYING for details.
--
--  This program is distributed WITHOUT ANY WARRANTY; without even the
--  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
--  PURPOSE.
--

register_command(
    "remote_export", "HOST[:PORT] SELECTOR [DIRECTORY]",
    "Exports a remote revision locally",
    "Exports the remote revision given by SELECTOR locally into "..
    "DIRECTORY. If no DIRECTORY is given, the branch name is used "..
    "as directory name.",
    "command_remote_export"
)

alias_command(
    "remote_export",
    "re"
)

function command_remote_export(host, selector, directory)
    --  argument sanity checking
    if host == nil or selector == nil then
        io.stderr:write("mtn: remote_export: missing arguments - "..
            "see 'mtn help remote_export'\n")
        return
    end

    -- we need 'automate remote' which is available as of mtn 0.46
    local res, version = mtn_automate("interface_version")
    if not res then
        io.stderr:write("mtn: remote_export: could not determine interface "..
                        "version of this version of monotone\n")
        return
    end

    version = tonumber(string.sub(version, 0, -4))
    if version < 12 then
        io.stderr:write("mtn: remote_export: this command needs a "..
                        "monotone with an automate interface version "..
                        "of 12.0 or higher (monotone 0.46 or higher)")
        return
    end

    local remotecall = function(host, ...)
        return mtn_automate("remote",
                            "--quiet",
                            "--remote-stdio-host="..host,
                            "--",
                            ...)
    end

    local splitlines = function(str)
        local t = {}
        local helper = function(line)
            if string.len(line) > 0 then
                table.insert(t, line)
            end
            return ""
        end
        helper((str:gsub("(.-)\r?\n", helper)))
        return t
    end

    local res, revs = remotecall(host, "select", selector)
    if not res then
        io.stderr:write("mtn: remote_export: could not resolve selector\n")
        return
    end

    revs = splitlines(revs)
    if table.maxn(revs) == 0 then
        io.stderr:write("mtn: remote_export: selector '"..selector.."' "..
                        "matches no revisions remotely\n")
        return
    end

    if table.maxn(revs) > 1 then
        io.stderr:write("mtn: remote_export: selector '"..selector.."' "..
                        "expands to more than one revision, please pick one:\n")
        for _,rev in ipairs(revs) do
            io.stderr:write("mtn: remote_export:   "..rev.."\n")
        end
        return
    end

    local rev = revs[1]

    if directory == nil then
        local res, str = remotecall(host, "certs", rev)
        if  not res then
            io.stderr:write("mtn: remote_export: could not query "..
                            "certs of "..rev.."\n")
            return
        end
        local basicio = parse_basic_io(str)
        local nextIsBranch = false
        for _,line in ipairs(basicio) do
            if line.name == "name" and line.values[1] == "branch" then
                nextIsBranch = true
            end

            if line.name == "value" and nextIsBranch then
                directory = line.values[1]
                break
            end
        end
    end

    if string.len(directory) == 0 then
        io.stderr:write("mtn: remote_export: invalid directory given\n")
        return
    end

    local rc = execute("mkdir", directory)
    if rc ~= 0 then
        io.stderr:write("mtn: remote_export: could not create directory '"..
                        directory.."'\n")
        return
    end

    local res, str = remotecall(host, "get_manifest_of", rev)
    if  not res then
        io.stderr:write("mtn: remote_export: could not query "..
                        "manifest of "..rev.."\n")
        return
    end

    local basicio = parse_basic_io(str)
    local dirs = {}
    local files = {}

    local filePath = nil
    for i,line in ipairs(basicio) do
        if line.name == "dir" and string.len(line.values[1]) > 0 then
            table.insert(dirs, line.values[1])
        end

        if line.name == "file" then
            filePath = line.values[1]
        end

        if line.name == "content" and filePath ~= nil then
            local executable = false
            if table.maxn(basicio) >= i+1 and
                basicio[i+1].name == "attr" and
                basicio[i+1].values[1] == "mtn:execute" and
                basicio[i+1].values[2] == "true" then
                executable = true
            end

            table.insert(files, {
                path = filePath,
                hash = line.values[1],
                exec = executable
            })
            filePath = nil
        end
    end

    io.stderr:write("mtn: remote_export: exporting '"..rev..
                    " ("..table.maxn(files).." files, "..table.maxn(dirs)..
                    " directories) to '"..directory.."'\n")

    for i,dir in ipairs(dirs) do
        local localPath = directory.. "/" .. dir;
        local rc = execute("mkdir", localPath)
        if rc ~= 0 then
            io.stderr:write("mtn: remote_export: could not create directory '"..
                            localPath.."\n")
            return
        end

        io.stderr:write("mtn: remote_export: ["..i.."/"..table.maxn(dirs).."] "
                        ..dir.." created\n")
    end

    for i,file in ipairs(files) do
        local res, contents = remotecall(host, "get_file", file.hash)
        if not res then
           io.stderr:write("mtn: remote_export: could not query contents of "..
                           "file '"..file.path.."' (hash: "..file.hash..")\n");
           return
        end

        local localPath = directory .. "/" .. file.path

        fp = io.open(localPath, "wb")
        fp:write(contents)
        fp:close()

        io.stderr:write("mtn: remote_export: ["..i.."/"..table.maxn(files).."] "
                        .. file.path .." fetched and saved\n")

        if file.exec then
            local rc = execute("chmod", "+x", localPath)
            if rc ~= 0 then
                io.stderr:write("mtn: remote_export: could not give '"..
                                localPath .."' executable privileges\n")
                return
            end

            io.stderr:write("mtn: remote_export: "..
                            "["..i.."/"..table.maxn(files).."] executable bit "..
                            "set on "..file.path.."\n")
        end
    end
end

