module("luci.controller.store", package.seeall)

local myopkg = "is-opkg"
local is_backup = "/usr/libexec/istore/backup"
local is_overlay_backup = "/usr/libexec/istore/overlay-backup"
local page_index = {"admin", "store", "pages"}

function index()
    local function store_api(action, onlypost)
        local e = entry({"admin", "store", action}, onlypost and post("store_action", {action = action}) or call("store_action", {action = action}))
        e.dependent = false -- 父节点不是必须的
        e.leaf = true -- 没有子节点
    end

    local action

    entry({"admin", "store"}, call("redirect_index"), _("iStore"), 31)
    entry({"admin", "store", "pages"}, call("store_index")).leaf = true
    if nixio.fs.access("/usr/lib/lua/luci/view/store/main_dev.htm") then
        entry({"admin", "store", "dev"}, call("store_dev")).leaf = true
    end
    entry({"admin", "store", "token"}, call("store_token"))
    entry({"admin", "store", "log"}, call("store_log"))
    entry({"admin", "store", "uid"}, call("action_user_id"))
    entry({"admin", "store", "upload"}, post("store_upload"))
    entry({"admin", "store", "check_self_upgrade"}, call("check_self_upgrade"))
    entry({"admin", "store", "do_self_upgrade"}, post("do_self_upgrade"))
    entry({"admin", "store", "toggle_docker"}, post("toggle_docker"))
    entry({"admin", "store", "toggle_arch"}, post("toggle_arch"))
    entry({"admin", "store", "get_block_devices"}, call("get_block_devices"))

    entry({"admin", "store", "configured"}, call("configured"))
    entry({"admin", "store", "entrysh"}, post("entrysh"))

    -- docker
    entry({"admin", "store", "docker_check_dir"}, call("docker_check_dir"))
    entry({"admin", "store", "docker_check_migrate"}, call("docker_check_migrate"))
    entry({"admin", "store", "docker_migrate"}, post("docker_migrate"))

    -- package
    for _, action in ipairs({"update", "install", "upgrade", "remove", "autoconf"}) do
        store_api(action, true)
    end
    for _, action in ipairs({"status", "installed"}) do
        store_api(action, false)
    end

    -- istore backup
    if nixio.fs.access("/usr/libexec/istore/backup") then
        entry({"admin", "store", "get_support_backup_features"}, call("get_support_backup_features"))
        entry({"admin", "store", "light_backup"}, post("light_backup"))
        entry({"admin", "store", "get_light_backup_file"}, call("get_light_backup_file"))
        entry({"admin", "store", "light_restore"}, post("light_restore"))
        entry({"admin", "store", "get_backup_app_list_file_path"}, call("get_backup_app_list_file_path"))
        entry({"admin", "store", "get_backup_app_list"}, call("get_backup_app_list"))
    end
    if nixio.fs.access("/usr/libexec/istore/backup") or nixio.fs.access("/usr/libexec/istore/overlay-backup") then
        entry({"admin", "store", "local_backup"}, post("local_backup"))
        entry({"admin", "store", "local_restore"}, post("local_restore"))
        entry({"admin", "store", "get_available_backup_file_list"}, call("get_available_backup_file_list"))
        entry({"admin", "store", "set_local_backup_dir_path"}, post("set_local_backup_dir_path"))
        entry({"admin", "store", "get_local_backup_dir_path"}, call("get_local_backup_dir_path"))
    end
end

local function user_id()
    local jsonc = require "luci.jsonc"
    local json_parse = jsonc.parse
    local fs   = require "nixio.fs"
	local data = fs.readfile("/etc/.app_store.id")

    local id
    if data ~= nil then
        id = json_parse(data)
    end
    if id == nil then
        fs.unlink("/etc/.app_store.id")
        id = {arch="",uid=""}
    end

    id.version = (fs.readfile("/etc/.app_store.version") or "?"):gsub("[\r\n]", "")

    return id
end

local function user_config()
    local uci  = require "luci.model.uci".cursor()

    local data = {
        hide_docker = uci:get("istore", "istore", "hide_docker") == "1",
        ignore_arch = uci:get("istore", "istore", "ignore_arch") == "1",
        last_path = uci:get("istore", "istore", "last_path"),
        super_arch = uci:get("istore", "istore", "super_arch"),
        channel = uci:get("istore", "istore", "channel")
    }
    return data
end

local function vue_lang()
    local i18n = require("luci.i18n")
    local lang = i18n.translate("istore_vue_lang")
    if lang == "istore_vue_lang" or lang == "" then
        lang = "en"
    end
    return lang
end

local function flock(file, type)
    local nixio = require "nixio"
    local oflags = nixio.open_flags("wronly", "creat")
    local lock, code, msg = nixio.open(file, oflags)
    if not lock then
        return nil, "Open lock failed: " .. msg
    end

    -- Acquire lock
    local stat, code, msg = lock:lock(type)
    if not stat then
        lock:close()
        return nil, "Lock failed: " .. msg
    end
    return lock, nil
end

local function is_exec(cmd, async)
    local nixio = require "nixio"
    local os   = require "os"
    local fs   = require "nixio.fs"
    local rshift  = nixio.bit.rshift

    local lock, msg = flock("/var/lock/istore.lock", "tlock")
    if lock == nil then
        return 255, "", msg
    end

    if async then
        cmd = "/etc/init.d/tasks task_add istore " .. luci.util.shellquote(cmd)
    end
    local r = os.execute(cmd .. " >/var/log/istore.stdout 2>/var/log/istore.stderr")
    local e = fs.readfile("/var/log/istore.stderr")
    local o = fs.readfile("/var/log/istore.stdout")

    fs.unlink("/var/log/istore.stderr")
    fs.unlink("/var/log/istore.stdout")

    lock:lock("ulock")
    lock:close()

    e = e or ""
    if r == 256 and e == "" then
        e = "os.execute exit code 1"
    end
    return rshift(r,8), o or "", e or ""
end

function redirect_index()
    luci.http.redirect(luci.dispatcher.build_url(unpack(page_index)))
end

function store_index()
    local fs   = require "nixio.fs"
    local features = { "_lua_force_array_" }
    if fs.access("/usr/libexec/istore/backup") then
        features[#features+1] = "backup"
    end
    if luci.sys.call(is_overlay_backup .. " supports_overlay_backup >/dev/null 2>&1") == 0 then
        features[#features+1] = "overlay"
    end
    if luci.sys.call("which docker >/dev/null 2>&1") == 0 then
        features[#features+1] = "docker"
    end
    if luci.sys.call("[ -d /ext_overlay ] >/dev/null 2>&1") == 0 then
        features[#features+1] = "sandbox"
    end
    if luci.sys.call("[ -f /www/luci-static/resources/luci.js ] >/dev/null 2>&1") == 0 then
        features[#features+1] = "luci-js"
    end
    luci.template.render("store/main", {prefix=luci.dispatcher.build_url(unpack(page_index)),id=user_id(),lang=vue_lang(),user_config=user_config(),features=features})
end

function store_dev()
    luci.template.render("store/main_dev", {prefix=luci.dispatcher.build_url(unpack({"admin", "store", "dev"})),id=user_id(),lang=vue_lang(),user_config=user_config()})
end

function store_log()
    local fs   = require "nixio.fs"
    local code = 0
    local e = fs.readfile("/var/log/istore.stderr")
    local o = fs.readfile("/var/log/istore.stdout")
    if o ~= nil then
        code = 206
    end
    luci.http.prepare_content("application/json")
    luci.http.write_json({code=code,stdout=o or "",stderr=e or ""})
end

function action_user_id()
    luci.http.prepare_content("application/json")
    luci.http.write_json(user_id())
end

function check_self_upgrade()
    local ret = {
        code = 500,
        msg = "Unknown"
    }
    local r,o,e = is_exec(myopkg .. " check_self_upgrade")
    if r ~= 0 then
        ret.msg = e
    else
        ret.code = o == "" and 304 or 200
        ret.msg = o:gsub("[\r\n]", "")
    end
    luci.http.prepare_content("application/json")
    luci.http.write_json(ret)
end

function do_self_upgrade()
    local code, out, err, ret
    code,out,err = is_exec(myopkg .. " do_self_upgrade")
    ret = {
        code = code,
        stdout = out,
        stderr = err
    }
    luci.http.prepare_content("application/json")
    luci.http.write_json(ret)
end

-- Internal action function
local function _action(exe, cmd, ...)

    local pkg = ""
    for k, v in pairs({...}) do
        pkg = pkg .. " " .. luci.util.shellquote(v)
    end

    local c = "%s %s %s" %{ exe, cmd, pkg }

    return is_exec(c, true)
end

function validate_pkgname(val)
	return (val ~= nil and val:match("^[a-zA-Z0-9_-]+$") ~= nil)
end

local function get_installed_and_cache()
    local metadir = "/usr/lib/opkg/meta"
    local cachedir = "/tmp/cache/istore"
    local cachefile = cachedir .. "/installed.json"
    local metapkgpre = "app-meta-"
    local nixio = require "nixio"
    local fs   = require "nixio.fs"
    local ipkg = require "luci.model.ipkg"
    local jsonc = require "luci.jsonc"
    local result = {}
    local lock, msg = flock("/var/lock/istore-installed.lock", "lock")
    local ms = fs.stat(metadir)
    local cs = fs.stat(cachefile)
    if not ms then
        result = {}
    elseif not cs or ms["mtime"] > cs["mtime"] then
        local itr = fs.dir(metadir)
        local data = {}
        if itr then
            local i18n = require("luci.i18n")
            local pkg
            for pkg in itr do
                if pkg:match("^.*%.json$") then
                    local metadata = fs.readfile(metadir .. "/" .. pkg)
                    if metadata ~= nil then
                        local meta = jsonc.parse(metadata)
                        if meta == nil then
                            local name = pkg:gsub("^(.-)%.json$", "%1")
                            meta = {
                                name = name,
                                title = "{ " .. name .. " }",
                                author = "<UNKNOWN>",
                                version = "0.0.0",
                                description = i18n.translate("This package is broken! Please reinstall or uninstall it."),
                                depends = {},
                                tags = {"broken"},
                                broken = true,
                            }
                        end
                        local metapkg = metapkgpre .. meta.name
                        local status = ipkg.status(metapkg)
                        if next(status) ~= nil then
                            meta.time = tonumber(status[metapkg]["Installed-Time"])
                            data[#data+1] = meta
                        end
                    end
                end
            end
        end
        result = data
        fs.mkdirr(cachedir)
        local oflags = nixio.open_flags("rdwr", "creat")
        local mfile, code, msg = nixio.open(cachefile, oflags)
        mfile:writeall(jsonc.stringify(result))
        mfile:close()
    else
        result = jsonc.parse(fs.readfile(cachefile) or "")
    end
    lock:lock("ulock")
    lock:close()
    return result
end

function store_action(param)
    local metadir = "/usr/lib/opkg/meta"
    local metapkgpre = "app-meta-"
    local code, out, err, ret
    local fs = require "nixio.fs"
    local ipkg = require "luci.model.ipkg"
    local jsonc = require "luci.jsonc"
    local json_parse = jsonc.parse
    local action = param.action or ""

    if action == "status" then
        local pkg = luci.http.formvalue("package")
        if not validate_pkgname(pkg) then
            luci.http.status(400, "Bad Request")
            return
        end
        local metapkg = metapkgpre .. pkg
        local meta = {}
        local metadata = fs.readfile(metadir .. "/" .. pkg .. ".json")

        if metadata ~= nil then
            meta = json_parse(metadata) or {}
        end
        meta.installed = false
        local status = ipkg.status(metapkg)
        if next(status) ~= nil then
            meta.installed=true
            meta.time=tonumber(status[metapkg]["Installed-Time"])
        end

        ret = meta
    elseif action == "installed" then
        local data = get_installed_and_cache()
        ret = data
    else
        local pkg = luci.http.formvalue("package")
        if not validate_pkgname(pkg) then
            luci.http.status(400, "Bad Request")
            return
        end
        local metapkg = pkg and (metapkgpre .. pkg) or ""
        if action == "update" or pkg then
            if action == "update" or action == "install" or action == "autoconf" then
                if (action == "install" and "1" == luci.http.formvalue("autoconf")) or action == "autoconf" then
                    local autoenv = "AUTOCONF=" .. pkg
                    local autopath = luci.http.formvalue("path")
                    local autoenable = luci.http.formvalue("enable")
                    if autopath ~= nil then
                        autoenv = autoenv .. " path=" .. luci.util.shellquote(autopath)
                        local uci  = require "luci.model.uci".cursor()
                        uci:set("istore", "istore", "last_path", autopath)
                        uci:commit("istore")
                    end
                    if autoenable ~= nil then
                        autoenv = autoenv .. " enable=" .. autoenable
                    end
                    code, out, err = _action(myopkg, luci.util.shellquote(autoenv), action, metapkg)
                else
                    code, out, err = _action(myopkg, action, metapkg)
                end
            else
                local meta = json_parse(fs.readfile(metadir .. "/" .. pkg .. ".json"))
                local pkgs = {}
                if meta == nil then
                    meta = {
                        depends = {},
                    }
                end
                if action == "upgrade" then
                    pkgs = meta.depends
                    table.insert(pkgs, metapkg)
                    code, out, err = _action(myopkg, action, unpack(pkgs))
                else -- remove
                    for _, dep in ipairs(meta.depends) do
                        if dep ~= "docker-deps" and dep ~= "luci-js-deps" then
                            pkgs[#pkgs+1] = dep
                        end
                    end
                    table.insert(pkgs, metapkg)
                    code, out, err = _action(myopkg, action, unpack(pkgs))
                    fs.unlink("/tmp/luci-indexcache")
                end
            end
        else
            code = 400
            err = "package is null"
        end

        ret = {
            code = code,
            stdout = out,
            stderr = err
        }
    end

    luci.http.prepare_content("application/json")
    luci.http.write_json(ret)
end

function store_token()
    luci.http.prepare_content("application/json")
    require "luci.template".render_string("{\"token\":\"<%=token%>\"}")
end

function store_upload()
    local fd
    local path
    local finished = false
    local tmpdir = "/tmp/is-root/tmp"
    luci.http.setfilehandler(
        function(meta, chunk, eof)
            if not fd then
                path = tmpdir .. "/" .. meta.file
                nixio.fs.mkdirr(tmpdir)
                fd = io.open(path, "w")
            end
            if chunk then
                fd:write(chunk)
            end
            if eof then
                fd:close()
                finished = true
            end
        end
    )
    local code, out, err
    out = ""
    if finished then
        if string.lower(string.sub(path, -4, -1)) == ".run" then
            code, out, err = _action("sh", "-c", "ls -l \"%s\"; md5sum \"%s\" 2>/dev/null; chmod 755 \"%s\" && \"%s\"; RET=$?; rm -f \"%s\"; exit $RET" %{ path, path, path, path, path })
        else
            code, out, err = _action("sh", "-c", "opkg install \"%s\"; RET=$?; rm -f \"%s\"; exit $RET" %{ path, path })
        end
    else
        code = 500
        err = "upload failed!"
    end
    --nixio.fs.unlink(path)
    local ret = {
        code = code,
        stdout = out,
        stderr = err
    }
    luci.http.prepare_content("application/json")
    luci.http.write_json(ret)
end

function configured()
    local uci = luci.http.formvalue("uci")
    if not validate_pkgname(uci) then
        luci.http.status(400, "Bad Request")
        return
    end
    local configured = nixio.fs.access("/etc/config/" .. uci)
    luci.http.prepare_content("application/json")
    luci.http.write_json({code=200, configured=configured})
end

function entrysh()
    local package = luci.http.formvalue("package")
    local update = luci.http.formvalue("update")
    local hostname = luci.http.formvalue("hostname")
    if hostname == nil or hostname == "" or not hostname:match("^[a-zA-Z0-9_%[][a-zA-Z0-9_%-%.%:%]]*$") then
        luci.http.status(400, "Bad Request")
        return
    end
    local nixio = require "nixio"
    local fs   = require "nixio.fs"
    local hostnameq = luci.util.shellquote(hostname)
    local cachedir = "/tmp/cache/istore/entrysh/" .. hostname
    fs.mkdirr(cachedir)

    local jsonc = require "luci.jsonc"
    local results = {}
    local errors = {}
    local force = update == "1"
    local candidate = nil
    if package ~= nil and package ~= "" then
        candidate = luci.util.split(package, ",")
    end
    local installed  = get_installed_and_cache()
    local lock, msg = flock("/var/lock/istore-entrysh.lock", "lock")
    local meta
    for _, meta in ipairs(installed) do
        if meta.flags ~= nil and meta.uci ~= nil and luci.util.contains(meta.flags, "entrysh")
            and (candidate == nil or luci.util.contains(candidate, meta.name)) then
            local entryfile = "/usr/libexec/istoree/" .. meta.name .. ".sh"
            local ucifile = "/etc/config/" .. meta.uci
            local cachefile = cachedir .. "/" .. meta.name .. ".json"
            local status = nil
            if not force then
                local us = fs.stat(ucifile)
                local cs = fs.stat(cachefile)
                if cs ~= nil and us["mtime"] <= cs["mtime"] then
                    status = jsonc.parse(fs.readfile(cachefile) or "")
                end
            end
            if status ~= nil then
                results[#results+1] = status
            elseif fs.access(entryfile) then
                local o = luci.util.exec(entryfile .. " status " .. hostnameq)
                if o == nil or o == "" then
                    errors[#errors+1] = {app=meta.name, code=500, msg="entrysh execute failed"}
                else
                    status = jsonc.parse(o)
                    if status == nil then
                        errors[#errors+1] = {app=meta.name, code=500, msg="json parse failed: " .. o}
                    else
                        results[#results+1] = status
                        local oflags = nixio.open_flags("rdwr", "creat")
                        local mfile, code, msg = nixio.open(cachefile, oflags)
                        mfile:writeall(jsonc.stringify(status))
                        mfile:close()
                    end
                end
            else
                errors[#errors+1] = {app=meta.name, code=404, msg="entrysh of this package not found"}
            end
        end
    end
    lock:lock("ulock")
    lock:close()

    luci.http.prepare_content("application/json")
    luci.http.write_json({code=200, status=results, errors=errors})
end

function docker_check_dir()
    local docker_on_system = luci.sys.call("/usr/libexec/istore/docker check_dir >/dev/null 2>&1") ~= 0
    luci.http.prepare_content("application/json")
    luci.http.write_json({code=200, docker_on_system=docker_on_system})
end

function docker_check_migrate()
    local path = luci.http.formvalue("path")
    if path == nil or path == "" then
        luci.http.status(400, "Bad Request")
        return
    end
    local r,o,e = is_exec("/usr/libexec/istore/docker migrate_check " .. luci.util.shellquote(path))
    local result = "good"
    if r == 1 then
        result = "bad"
    elseif r == 2 then
        result = "existed"
    end
    luci.http.prepare_content("application/json")
    luci.http.write_json({code=200, result=result, error=e})
end

function docker_migrate()
    local path = luci.http.formvalue("path")
    if path == nil or path == "" then
        luci.http.status(400, "Bad Request")
        return
    end

    local action = "migrate"
    local overwrite = luci.http.formvalue("overwrite")
    if overwrite == "chdir" then
        action = "change_dir"
    end
    local r,o,e = is_exec("/usr/libexec/istore/docker " .. action .. " " .. luci.util.shellquote(path), true)
    luci.http.prepare_content("application/json")
    luci.http.write_json({code=r, stdout=o, stderr=e})
end

local function split(str,reps)
    local resultStrList = {}
    string.gsub(str,'[^'..reps..']+',function (w)
        table.insert(resultStrList,w)
    end)
    return resultStrList
end

local function ltn12_popen(command)

	local fdi, fdo = nixio.pipe()
	local pid = nixio.fork()

	if pid > 0 then
		fdo:close()
		local close
		return function()
			local buffer = fdi:read(2048)
			local wpid, stat = nixio.waitpid(pid, "nohang")
			if not close and wpid and stat == "exited" then
				close = true
			end

			if buffer and #buffer > 0 then
				return buffer
			elseif close then
				fdi:close()
				return nil
			end
		end
	elseif pid == 0 then
		nixio.dup(fdo, nixio.stdout)
		fdi:close()
		fdo:close()
		nixio.exec("/bin/sh", "-c", command)
	end
end

-- call get_support_backup_features
function get_support_backup_features()
    local jsonc = require "luci.jsonc"
    local error_ret = {code = 500, msg = "Unknown"}
    local success_ret = {code = 200, msg = "Unknown"}
    local r,o,e = is_exec(is_backup .. " get_support_backup_features")
    if r ~= 0 then
        error_ret.msg = e
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    else
        success_ret.code = 200
        success_ret.msg = jsonc.stringify(split(o,'\n'))
        luci.http.prepare_content("application/json")
        luci.http.write_json(success_ret)
    end
end

-- post light_backup
function light_backup()
    local jsonc = require "luci.jsonc"
    local error_ret = {code = 500, msg = "Unknown"}
    local success_ret = {code = 200,msg = "Unknown"}
    local r,o,e = is_exec(is_backup .. " backup")

    if r ~= 0 then
        error_ret.msg = e
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    else
        success_ret.code = 200
        success_ret.msg = o:gsub("[\r\n]", "")
        luci.http.prepare_content("application/json")
        luci.http.write_json(success_ret)
    end
end

-- call get_light_backup_file
function get_light_backup_file()
    local light_backup_cmd  = "tar -c %s | gzip 2>/dev/null"
    local loght_backup_filelist = "/etc/istore/app.list"
    local reader = ltn12_popen(light_backup_cmd:format(loght_backup_filelist))
    luci.http.header('Content-Disposition', 'attachment; filename="light-backup-%s-%s.tar.gz"' % {
        luci.sys.hostname(), os.date("%Y-%m-%d")})
    luci.http.prepare_content("application/x-targz")
    luci.ltn12.pump.all(reader, luci.http.write)
end

local function update_local_backup_path(path)
    local uci = require "uci"
    local fs = require "nixio.fs"
    local x = uci.cursor()
    local local_backup_path

    if fs.access("/etc/config/istore") then
        local_backup_path = x:get("istore","istore","local_backup_path")
    else
        --create config file
        local f=io.open("/etc/config/istore","a+")
        f:write("config istore \'istore\'\n\toption local_backup_path \'\'")
        f:flush()
        f:close()
    end

    if path ~= local_backup_path then
        -- set uci config
        x:set("istore","istore","local_backup_path",path)
        x:commit("istore")
    end
end

-- post local_backup
function local_backup()
    local code, out, err, ret
    local error_ret
    local path = luci.http.formvalue("path")
    local type = luci.http.formvalue("type") or "istore"
    if path ~= "" then
        -- judge path
        local fs   = require "nixio.fs"
        fs.mkdirr(path)
        code,out,err = is_exec("findmnt -T " .. path .. " -o TARGET|sed -n 2p")
        local mp = out:gsub("[\r\n]", "")
        if mp == "/" or mp == "/tmp" then
            -- error
            error_ret = {code = 500, stderr = "Path Error,Can not be / or tmp."}
            luci.http.prepare_content("application/json")
            luci.http.write_json(error_ret)
        elseif type == "overlay" and ( mp == "/overlay" or mp == "/ext_overlay" ) then
            -- error
            error_ret = {code = 500, stderr = "Path Error,Can not be /overlay or /ext_overlay."}
            luci.http.prepare_content("application/json")
            luci.http.write_json(error_ret)
        else
            -- update local backup path
            update_local_backup_path(path)
            code,out,err = _action(type == "overlay" and is_overlay_backup or is_backup, "backup", path)
            ret = {
                code = code,
                stdout = out,
                stderr = err
            }
            luci.http.prepare_content("application/json")
            luci.http.write_json(ret)
        end
    else
        -- error
        error_ret = {code = 500, stderr = "Path Unknown"}
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    end
end

-- post light_restore
function light_restore()
    local fd
    local path
    local finished = false
    local tmpdir = "/tmp/"
    luci.http.setfilehandler(
        function(meta, chunk, eof)
            if not fd then
                path = tmpdir .. "/" .. meta.file
                fd = io.open(path, "w")
            end
            if chunk then
                fd:write(chunk)
            end
            if eof then
                fd:close()
                finished = true
            end
        end
    )

    local code, out, err, ret

    if finished then
        is_exec("rm /etc/istore/app.list;tar -xzf " .. path .. " -C /")
        nixio.fs.unlink(path)
        if nixio.fs.access("/etc/istore/app.list") then
            code,out,err = _action(is_backup, "restore")
            ret = {
                code = code,
                stdout = out,
                stderr = err
            }
            luci.http.prepare_content("application/json")
            luci.http.write_json(ret)
        else
            local error_ret = {code = 500, stderr = "File is error!"}
            luci.http.prepare_content("application/json")
            luci.http.write_json(error_ret)
        end
    else
        ret = {code = 500, stderr = "upload failed!"}
        luci.http.prepare_content("application/json")
        luci.http.write_json(ret)
    end
end

-- post local_restore
function local_restore()
    local path = luci.http.formvalue("path")
    local type = luci.http.formvalue("type") or "istore"
    local code, out, err, ret
    if path ~= "" then
        code,out,err = _action(type == "overlay" and is_overlay_backup or is_backup, "restore", path)
        ret = {
            code = code,
            stdout = out,
            stderr = err
        }
        luci.http.prepare_content("application/json")
        luci.http.write_json(ret)
    else
        -- error
        error_ret = {code = 500, stderr = "Path Unknown"}
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    end
end

-- call get_backup_app_list_file_path
function get_backup_app_list_file_path()
    local jsonc = require "luci.jsonc"
    local error_ret = {code = 500, msg = "Unknown"}
    local success_ret = {code = 200,msg = "Unknown"}
    local r,o,e = is_exec(is_backup .. " get_backup_app_list_file_path")
    if r ~= 0 then
        error_ret.msg = e
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    else
        success_ret.code = 200
        success_ret.msg = o:gsub("[\r\n]", "")
        luci.http.prepare_content("application/json")
        luci.http.write_json(success_ret)
    end
end

-- call get_backup_app_list
function get_backup_app_list()
    local jsonc = require "luci.jsonc"
    local error_ret = {code = 500, msg = "Unknown"}
    local success_ret = {code = 200,msg = "Unknown"}
    local r,o,e = is_exec(is_backup .. " get_backup_app_list")
    if r ~= 0 then
        error_ret.msg = e
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    else
        success_ret.code = 200
        success_ret.msg = jsonc.stringify(split(o,'\n'))
        luci.http.prepare_content("application/json")
        luci.http.write_json(success_ret)
    end
end

-- call get_available_backup_file_list
function get_available_backup_file_list()
    local jsonc = require "luci.jsonc"
    local error_ret = {code = 500, msg = "Unknown"}
    local success_ret = {code = 200,msg = "Unknown"}
    local path = luci.http.formvalue("path")
    local type = luci.http.formvalue("type") or "istore"
    local r,o,e

    if path ~= "" then
        -- update local backup path
        update_local_backup_path(path)
        r,o,e = is_exec((type == "overlay" and is_overlay_backup or is_backup) .. " get_available_backup_file_list " .. luci.util.shellquote(path))
        if r ~= 0 then
            error_ret.msg = e
            luci.http.prepare_content("application/json")
            luci.http.write_json(error_ret)
        else
            success_ret.code = 200
            success_ret.msg = jsonc.stringify(split(o,'\n'))
            luci.http.prepare_content("application/json")
            luci.http.write_json(success_ret)
        end
    else
        -- set error code
        error_ret.msg = "Path Unknown"
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    end
end

-- post set_local_backup_dir_path
function set_local_backup_dir_path()
    local path = luci.http.formvalue("path")
    local success_ret = {code = 200, msg = "Success"}
    local error_ret = {code = 500, msg = "Unknown"}

    if path ~= "" then
        -- update local backup path
        update_local_backup_path(path)
        luci.http.prepare_content("application/json")
        luci.http.write_json(success_ret)
    else
        -- set error code
        error_ret.msg = "Path Unknown"
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    end        
end

-- call get_local_backup_dir_path
function get_local_backup_dir_path()
    local uci = require "uci"
    local fs = require "nixio.fs"
    local x = uci.cursor()
    local local_backup_path = nil
    local success_ret = {code = 200,msg = "Unknown"}
    local error_ret = {code = 500, msg = "Path Unknown"}

    if fs.access("/etc/config/istore") then
        local_backup_path = x:get("istore","istore","local_backup_path")
        if local_backup_path == nil then
            luci.http.prepare_content("application/json")
            luci.http.write_json(error_ret)
        else
            success_ret.msg = local_backup_path:gsub("[\r\n]", "")
            luci.http.prepare_content("application/json")
            luci.http.write_json(success_ret)
        end 
    else
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    end
end

-- copy from /usr/lib/lua/luci/model/diskman.lua
local function byte_format(byte)
  local suff = {"B", "KB", "MB", "GB", "TB"}
  for i=1, 5 do
    if byte > 1024 and i < 5 then
      byte = byte / 1024
    else
      return string.format("%.2f %s", byte, suff[i]) 
    end 
  end
end

-- copy from /usr/libexec/rpcd/luci
local function getBlockDevices()
    local fs = require "nixio.fs"

    local block = io.popen("/sbin/block info", "r")
    if block then
        local rv = {}

        while true do
            local ln = block:read("*l")
            if not ln then
                break
            end

            local dev = ln:match("^/dev/(.-):")
            if dev then
                local s = tonumber((fs.readfile("/sys/class/block/" .. dev .."/size")))
                local e = {
                    dev = "/dev/" .. dev,
                    size = s and byte_format(s * 512)
                }

                local key, val = { }
                for key, val in ln:gmatch([[(%w+)="(.-)"]]) do
                    e[key:lower()] = val
                end

                rv[dev] = e
            end
        end

        block:close()

        return rv
    else
        return
    end
end

function get_block_devices()
    local error_ret = {code = 500, msg = "Unable to execute block utility"}
    local devices = getBlockDevices()
    if devices ~= nil then
        luci.http.prepare_content("application/json")
        luci.http.write_json({code = 200, data = devices})
    else
        luci.http.prepare_content("application/json")
        luci.http.write_json(error_ret)
    end
end

function toggle_docker()
    local uci  = require "luci.model.uci".cursor()
    local hide = luci.http.formvalue("hide")
    uci:set("istore", "istore", "hide_docker", hide == "true" and "1" or "0")
    uci:commit("istore")
    luci.http.prepare_content("application/json")
    luci.http.write_json({code = 200, msg = "Success"})
end

function toggle_arch()
    local uci  = require "luci.model.uci".cursor()
    local ignore = luci.http.formvalue("ignore")
    uci:set("istore", "istore", "ignore_arch", ignore == "true" and "1" or "0")
    uci:commit("istore")
    luci.http.prepare_content("application/json")
    luci.http.write_json({code = 200, msg = "Success"})
end
