2024-07-28 03:14:53 +03:00
|
|
|
local function fail(s, ...) error(string.format(s, ...)) end
|
|
|
|
|
|
|
|
local M = {}
|
|
|
|
|
|
|
|
function M:setup()
|
|
|
|
ps.sub_remote("extract", function(args)
|
2024-09-14 16:19:17 +03:00
|
|
|
local noisy = #args == 1 and ' "" --noisy' or ' ""'
|
2024-07-28 03:14:53 +03:00
|
|
|
for _, arg in ipairs(args) do
|
|
|
|
ya.manager_emit("plugin", { self._id, args = ya.quote(arg, true) .. noisy })
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
function M:entry(args)
|
|
|
|
if not args[1] then
|
|
|
|
fail("No URL provided")
|
|
|
|
end
|
|
|
|
|
2024-09-14 16:19:17 +03:00
|
|
|
local from, to, pwd = Url(args[1]), args[2] ~= "" and Url(args[2]) or nil, ""
|
2024-07-28 03:14:53 +03:00
|
|
|
while true do
|
2024-09-14 16:19:17 +03:00
|
|
|
if not M:try_with(from, pwd, to) then
|
2024-07-28 03:14:53 +03:00
|
|
|
break
|
2024-09-14 16:19:17 +03:00
|
|
|
elseif args[3] ~= "--noisy" then
|
2024-07-28 03:14:53 +03:00
|
|
|
fail("'%s' is password-protected, please extract it individually and enter the password", args[1])
|
|
|
|
end
|
|
|
|
|
|
|
|
local value, event = ya.input {
|
2024-09-14 16:19:17 +03:00
|
|
|
title = string.format('Password for "%s":', from:name()),
|
2024-07-28 03:14:53 +03:00
|
|
|
position = { "center", w = 50 },
|
|
|
|
}
|
|
|
|
if event == 1 then
|
|
|
|
pwd = value
|
|
|
|
else
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-09-14 16:19:17 +03:00
|
|
|
function M:try_with(from, pwd, to)
|
|
|
|
to = to or from:parent()
|
|
|
|
if not to then
|
|
|
|
fail("Invalid URL '%s'", from)
|
2024-07-28 03:14:53 +03:00
|
|
|
end
|
|
|
|
|
2024-09-14 16:19:17 +03:00
|
|
|
local tmp = fs.unique_name(to:join(self.tmp_name(from)))
|
2024-07-28 03:14:53 +03:00
|
|
|
if not tmp then
|
2024-09-14 16:19:17 +03:00
|
|
|
fail("Failed to determine a temporary directory for %s", from)
|
2024-07-28 03:14:53 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
local archive = require("archive")
|
2024-10-20 21:03:56 +03:00
|
|
|
local child, code = archive.spawn_7z { "x", "-aou", "-sccUTF-8", "-p" .. pwd, "-o" .. tostring(tmp), tostring(from) }
|
2024-07-28 03:14:53 +03:00
|
|
|
if not child then
|
|
|
|
fail("Spawn `7z` and `7zz` both commands failed, error code %s", code)
|
|
|
|
end
|
|
|
|
|
|
|
|
local output, err = child:wait_with_output()
|
2024-09-20 20:47:34 +03:00
|
|
|
if output and output.status.code == 2 and archive.is_encrypted(output.stderr) then
|
2024-07-28 03:14:53 +03:00
|
|
|
fs.remove("dir_clean", tmp)
|
2024-09-14 13:46:38 +03:00
|
|
|
return true -- Need to retry
|
2024-07-28 03:14:53 +03:00
|
|
|
end
|
|
|
|
|
2024-09-14 16:19:17 +03:00
|
|
|
self:tidy(from, to, tmp)
|
2024-07-28 03:14:53 +03:00
|
|
|
if not output then
|
2024-09-14 16:19:17 +03:00
|
|
|
fail("7zip failed to output when extracting '%s', error code %s", err, from)
|
2024-07-28 03:14:53 +03:00
|
|
|
elseif output.status.code ~= 0 then
|
2024-09-14 16:19:17 +03:00
|
|
|
fail("7zip exited when extracting '%s', error code %s", from, output.status.code)
|
2024-07-28 03:14:53 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-09-14 16:19:17 +03:00
|
|
|
function M:tidy(from, to, tmp)
|
|
|
|
local outs = fs.read_dir(tmp, { limit = 2 })
|
|
|
|
if not outs then
|
|
|
|
fail("Failed to read the temporary directory '%s' when extracting '%s'", tmp, from)
|
|
|
|
elseif #outs == 0 then
|
2024-07-28 03:14:53 +03:00
|
|
|
fs.remove("dir", tmp)
|
2024-09-14 16:19:17 +03:00
|
|
|
fail("No files extracted from '%s'", from)
|
|
|
|
end
|
|
|
|
|
|
|
|
local only = #outs == 1
|
2024-09-20 20:47:34 +03:00
|
|
|
if only and not outs[1].cha.is_dir and require("archive").is_tar(outs[1].url) then
|
2024-09-14 16:19:17 +03:00
|
|
|
self:entry { tostring(outs[1].url), tostring(to) }
|
|
|
|
fs.remove("file", outs[1].url)
|
|
|
|
fs.remove("dir", tmp)
|
|
|
|
return
|
2024-07-28 03:14:53 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
local target
|
2024-09-14 16:19:17 +03:00
|
|
|
if only then
|
|
|
|
target = to:join(outs[1].name)
|
2024-07-28 03:14:53 +03:00
|
|
|
else
|
2024-09-14 16:19:17 +03:00
|
|
|
target = to:join(self.trim_ext(from:name()))
|
2024-07-28 03:14:53 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
target = fs.unique_name(target)
|
|
|
|
if not target then
|
2024-09-14 16:19:17 +03:00
|
|
|
fail("Failed to determine a target for '%s'", from)
|
2024-07-28 03:14:53 +03:00
|
|
|
end
|
|
|
|
|
2024-09-14 16:19:17 +03:00
|
|
|
target = tostring(target)
|
|
|
|
if only and not os.rename(tostring(outs[1].url), target) then
|
|
|
|
fail('Failed to move "%s" to "%s"', outs[1].url, target)
|
|
|
|
elseif not only and not os.rename(tostring(tmp), target) then
|
2024-07-28 03:14:53 +03:00
|
|
|
fail('Failed to move "%s" to "%s"', tmp, target)
|
|
|
|
end
|
|
|
|
fs.remove("dir", tmp)
|
|
|
|
end
|
|
|
|
|
2024-10-25 11:27:26 +03:00
|
|
|
function M.tmp_name(url) return ".tmp_" .. ya.hash(string.format("extract//%s//%.10f", url, ya.time())) end
|
2024-07-28 03:14:53 +03:00
|
|
|
|
|
|
|
function M.trim_ext(name)
|
|
|
|
-- stylua: ignore
|
|
|
|
local exts = { ["7z"] = true, apk = true, bz2 = true, bzip2 = true, exe = true, gz = true, gzip = true, iso = true, jar = true, rar = true, tar = true, tgz = true, xz = true, zip = true, zst = true }
|
|
|
|
|
|
|
|
while true do
|
|
|
|
local s = name:gsub("%.([a-zA-Z0-9]+)$", function(s) return (exts[s] or exts[s:lower()]) and "" end)
|
|
|
|
if s == name or s == "" then
|
|
|
|
break
|
|
|
|
else
|
|
|
|
name = s
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return name
|
|
|
|
end
|
|
|
|
|
|
|
|
return M
|