diff --git a/etc/get.lua b/etc/get.lua index 4a17cfc..13f983b 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -1,33 +1,73 @@ -assert(dofile("../lua/buffer.lua")) +-- this examples needs it all +assert(dofile("../lua/code.lua")) assert(dofile("../lua/ftp.lua")) -assert(dofile("../lua/base64.lua")) +assert(dofile("../lua/concat.lua")) +assert(dofile("../lua/url.lua")) assert(dofile("../lua/http.lua")) --- format a number of bytes per second into a human readable form -function strbps(b) - local l = "B/s" - if b > 1024 then - b = b / 1024 - l = "KB/s" - if b > 1024 then - b = b / 1024 - l = "MB/s" - if b > 1024 then - b = b / 1024 - l = "GB/s" -- hmmm +-- formats a number of seconds into human readable form +function nicetime(s) + local l = "s" + if s > 60 then + s = s / 60 + l = "m" + if s > 60 then + s = s / 60 + l = "h" + if s > 24 then + s = s / 24 + l = "d" -- hmmm end end end - return format("%.2f%s ", b, l) + if l == "s" then return format("%2.0f%s", s, l) + else return format("%5.2f%s", s, l) end +end + +-- formats a number of bytes into human readable form +function nicesize(b) + local l = "B" + if b > 1024 then + b = b / 1024 + l = "KB" + if b > 1024 then + b = b / 1024 + l = "MB" + if b > 1024 then + b = b / 1024 + l = "GB" -- hmmm + end + end + end + return format("%7.2f%2s", b, l) +end + +-- returns a string with the current state of the download +function gauge(got, dt, size) + local rate = got / dt + if size and size >= 1 then + return format("%s received, %s/s throughput, " .. + "%.0f%% done, %s remaining", + nicesize(got), + nicesize(rate), + 100*got/size, + nicetime((size-got)/rate)) + else + return format("%s received, %s/s throughput, %s elapsed", + nicesize(got), + nicesize(rate), + nicetime(dt)) + end end -- creates a new instance of a receive_cb that saves to disk -- kind of copied from luasocket's manual callback examples -function receive2disk(file) +function receive2disk(file, size) local aux = { start = _time(), got = 0, - file = openfile(file, "wb") + file = openfile(file, "wb"), + size = size } local receive_cb = function(chunk, err) local dt = _time() - %aux.start -- elapsed time since start @@ -39,62 +79,69 @@ function receive2disk(file) write(%aux.file, chunk) %aux.got = %aux.got + strlen(chunk) -- total bytes received if dt < 0.1 then return 1 end -- not enough time for estimate - local rate = %aux.got / dt -- get download rate - write("\r" .. strbps(rate)) -- print estimate + write("\r", gauge(%aux.got, dt, %aux.size)) return 1 end return receive_cb end --- stolen from http implementation -function split_url(url, default) - -- initialize default parameters - local parsed = default or {} - -- get scheme - url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) - -- get user name and password. both can be empty! - -- moreover, password can be ommited - url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) - %parsed.user = u - -- there can be an empty password, but the ':' has to be there - -- or else there is no password - %parsed.pass = nil -- kill default password - if c == ":" then %parsed.pass = p end - end) - -- get host - url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) - -- get port if any - url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) - -- whatever is left is the path - if url ~= "" then parsed.path = url end - return parsed -end - --- stolen from http implementation -function get_statuscode(line) - local _,_, code = strfind(line, " (%d%d%d) ") - return tonumber(code) -end - +-- downloads a file using the ftp protocol function getbyftp(url, file) - local err = ftp_getindirect(url, receive2disk(file), "b") - if err then print(err) else print("done.") end + local err = FTP.get_cb { + url = url, + content_cb = receive2disk(file), + type = "i" + } + print() + if err then print(err) end end -function getbyhttp(url, file) - local hdrs, line, err = http_getindirect(url, receive2disk(file)) - if line and get_statuscode(line) == 200 then print("done.") - elseif line then print(line) else print(err) end +-- downloads a file using the http protocol +function getbyhttp(url, file, size) + local response = HTTP.request_cb( + {url = url}, + {body_cb = receive2disk(file, size)} + ) + print() + if response.code ~= 200 then print(response.status or response.error) end end -function get(url, file) - local parsed = split_url(url) - if parsed.scheme == "ftp" then getbyftp(url, file) - else getbyhttp(url, file) end +-- determines the size of a http file +function gethttpsize(url) + local response = HTTP.request { + method = "HEAD", + url = url + } + if response.code == 200 then + return tonumber(response.headers["content-length"]) + end end +-- determines the scheme and the file name of a given url +function getschemeandname(url, name) + -- this is an heuristic to solve a common invalid url poblem + if not strfind(url, "//") then url = "//" .. url end + local parsed = URL.parse_url(url, {scheme = "http"}) + if name then return parsed.scheme, name end + local segment = URL.parse_path(parsed.path) + name = segment[getn(segment)] + if segment.is_directory then name = nil end + return parsed.scheme, name +end + +-- gets a file either by http or url, saving as name +function get(url, name) + local scheme + scheme, name = getschemeandname(url, name) + if not name then print("unknown file name") + elseif scheme == "ftp" then getbyftp(url, name) + elseif scheme == "http" then getbyhttp(url, name, gethttpsize(url)) + else print("unknown scheme" .. scheme) end +end + +-- main program arg = arg or {} -if getn(arg) < 2 then - write("Usage:\n luasocket -f get.lua \n") +if getn(arg) < 1 then + write("Usage:\n luasocket -f get.lua []\n") exit(1) else get(arg[1], arg[2]) end