SHOW:
|
|
- or go back to the newest paste.
1 | local component = require("component") | |
2 | ||
3 | if not component.isAvailable("internet") then | |
4 | io.stderr:write("OpenFTP requires an Internet Card to run!\n") | |
5 | return | |
6 | end | |
7 | ||
8 | local internet = require("internet") | |
9 | local computer = require("computer") | |
10 | local unicode = require("unicode") | |
11 | local shell = require("shell") | |
12 | local event = require("event") | |
13 | local term = require("term") | |
14 | local text = require("text") | |
15 | local fs = require("filesystem") | |
16 | local inetc = component.internet | |
17 | local gpu = component.gpu | |
18 | ||
19 | -- Variables ------------------------------------------------------------------- | |
20 | ||
21 | local isColored, isVerbose | |
22 | local args, options | |
23 | ||
24 | local sock, host, port, timer | |
25 | local w = gpu.getResolution() | |
26 | ||
27 | local history = {} | |
28 | local commands = {} | |
29 | ||
30 | local chsize = 102400 | |
31 | local isRunning = true | |
32 | ||
33 | -- Functions ------------------------------------------------------------------- | |
34 | ||
35 | local function setFG(fg) | |
36 | if isColored then | |
37 | gpu.setForeground(fg) | |
38 | end | |
39 | end | |
40 | ||
41 | local function nop() end | |
42 | ||
43 | local function help() | |
44 | print("Usage: ftp [--colors=<always|never|auto>] <host> [port]") | |
45 | print() | |
46 | print("Options: ") | |
47 | print(" --colors=<always|never|auto> Specify whether to use color or not.") | |
48 | ||
49 | os.exit(0) | |
50 | end | |
51 | ||
52 | local function init(...) | |
53 | args, options = shell.parse(...) | |
54 | ||
55 | local oColors = options["colors"] or "auto" | |
56 | oColors = (oColors == "always" or oColors == "never") and oColors or "auto" | |
57 | ||
58 | if oColors == "always" then | |
59 | isColored = true | |
60 | elseif oColors == "never" then | |
61 | isColored = false | |
62 | elseif oColors == "auto" then | |
63 | isColored = gpu.getDepth() > 1 | |
64 | end | |
65 | ||
66 | isVerbose = options["verbose"] == true | |
67 | host, port = args[1] or nil, args[2] or 21 | |
68 | ||
69 | if #args < 1 then help() end | |
70 | end | |
71 | ||
72 | local function connect() | |
73 | local lSock, reason = internet.open(host .. ":" .. port) | |
74 | if not lSock then | |
75 | io.stderr:write(("ftp: %s: %s\n"):format(host .. ":" .. port, | |
76 | reason or "unknown error")) | |
77 | os.exit(1) | |
78 | return | |
79 | end | |
80 | ||
81 | sock = lSock | |
82 | sock:setTimeout(0.2) | |
83 | end | |
84 | ||
85 | local read | |
86 | ||
87 | local function lost() | |
88 | read(trace, true) | |
89 | setFG(0xFF0000) | |
90 | print("Connection lost.") | |
91 | setFG(0xFFFFFF) | |
92 | sock:close() | |
93 | os.exit(0) | |
94 | end | |
95 | ||
96 | local function readLine() | |
97 | local ok, line = pcall(sock.read, sock) | |
98 | if ok and line == "" then lost() end | |
99 | ||
100 | return ok and line or false | |
101 | end | |
102 | ||
103 | function read(f, nwait) | |
104 | local was, lastRet, out = false, nil, computer.uptime() + 2 | |
105 | ||
106 | repeat | |
107 | local line = readLine() | |
108 | ||
109 | if line then | |
110 | lastRet = f(line) | |
111 | was = true | |
112 | out = computer.uptime() + 2 | |
113 | end | |
114 | ||
115 | if computer.uptime() >= out then | |
116 | lost() | |
117 | end | |
118 | until not line and (was or nwait) | |
119 | ||
120 | return was, lastRet | |
121 | end | |
122 | ||
123 | local function parseOutput(str) | |
124 | local match = {str:match("^(%d%d%d)([ -])(.*)$")} | |
125 | ||
126 | if #match < 1 then return false end | |
127 | ||
128 | local code = tonumber(match[1]) | |
129 | local codeColor do | |
130 | if code >= 100 and code < 200 then | |
131 | codeColor = 0x0000FF | |
132 | elseif code >= 200 and code < 300 then | |
133 | codeColor = 0x00FF00 | |
134 | elseif code >= 300 and code < 400 then | |
135 | codeColor = 0xFFFF00 | |
136 | else | |
137 | codeColor = 0xFF0000 | |
138 | end | |
139 | end | |
140 | local isLast = match[2] == " " | |
141 | local text = match[3] | |
142 | ||
143 | return { | |
144 | code = code, | |
145 | codeColor = codeColor, | |
146 | isLast = isLast, | |
147 | text = text | |
148 | } | |
149 | end | |
150 | ||
151 | local function traceLine(data) | |
152 | setFG(data.codeColor) | |
153 | io.write(data.code) | |
154 | setFG(0x666999) | |
155 | io.write(data.isLast and " " or "-") | |
156 | setFG(0xFFFFFF) | |
157 | io.write(data.text .. "\n") | |
158 | end | |
159 | ||
160 | local function trace(str) | |
161 | local data = parseOutput(str) | |
162 | if data then | |
163 | traceLine(data) | |
164 | return data | |
165 | else | |
166 | print(str) | |
167 | end | |
168 | end | |
169 | ||
170 | local function exit() | |
171 | if sock then | |
172 | sock:write("QUIT\r\n") | |
173 | sock:flush() | |
174 | read(trace, true) | |
175 | sock:close() | |
176 | end | |
177 | ||
178 | setFG(0xFFFFFF) | |
179 | ||
180 | os.exit(0) | |
181 | end | |
182 | ||
183 | local function auth() | |
184 | read(trace) | |
185 | ||
186 | local got = false | |
187 | while true do | |
188 | local user repeat | |
189 | io.write("Name: ") | |
190 | user = term.read() | |
191 | if not user then | |
192 | print() | |
193 | end | |
194 | until user and #user > 0 | |
195 | sock:write("USER " .. user .. "\r\n") | |
196 | sock:flush() | |
197 | ||
198 | local got = false | |
199 | ||
200 | read(function (str) | |
201 | local data = trace(str) | |
202 | if not got then | |
203 | got = data and data.code == 331 and data.isLast | |
204 | end | |
205 | end) | |
206 | ||
207 | if got then break end | |
208 | end | |
209 | ||
210 | io.write("Password: ") | |
211 | pass = term.read(nil, nil, nil, "*"):sub(1, -2) | |
212 | if not pass then | |
213 | print() | |
214 | end | |
215 | sock:write("PASS " .. (pass or "") .. "\r\n") | |
216 | sock:flush() | |
217 | ||
218 | local logined = false | |
219 | ||
220 | while true do | |
221 | local was = read(function (str) | |
222 | local data = trace(str) | |
223 | logined = data and data.code == 230 and data.isLast | |
224 | end) | |
225 | if was then break end | |
226 | end | |
227 | ||
228 | if not logined then | |
229 | exit() | |
230 | end | |
231 | ||
232 | print("Using binary mode to transfer files.") | |
233 | sock:write("TYPE I\r\n") | |
234 | sock:flush() | |
235 | read(nop) | |
236 | end | |
237 | ||
238 | local function pasv() | |
239 | sock:write("PASV\r\n") | |
240 | local ip, port | |
241 | ||
242 | local was, ret = read(function (str) | |
243 | local data = trace(str) | |
244 | if data and data.code == 227 and data.isLast then | |
245 | local match = {data.text:match("(%d+),(%d+),(%d+),(%d+),(%d+),(%d+)")} | |
246 | if match then | |
247 | ip = table.concat({match[1], match[2], match[3], match[4]}, ".") | |
248 | port = tonumber(match[5]) * 256 + tonumber(match[6]) | |
249 | end | |
250 | end | |
251 | end) | |
252 | ||
253 | if not ip or not port then | |
254 | return false | |
255 | end | |
256 | ||
257 | return inetc.connect(ip, port) | |
258 | end | |
259 | ||
260 | local function readPasv(pasvSock, f) | |
261 | os.sleep(0.2) | |
262 | ||
263 | local buf = {} | |
264 | local bufLen = 0 | |
265 | local written = false | |
266 | ||
267 | while true do | |
268 | local chunk = pasvSock.read(chsize) | |
269 | ||
270 | if bufLen >= chsize and written then | |
271 | buf = {} | |
272 | bufLen = 0 | |
273 | end | |
274 | ||
275 | if chunk then | |
276 | table.insert(buf, chunk) | |
277 | bufLen = bufLen + #chunk | |
278 | written = false | |
279 | end | |
280 | ||
281 | if not written and (bufLen >= chsize or not chunk) then | |
282 | f(table.concat(buf), bufLen) | |
283 | written = true | |
284 | end | |
285 | ||
286 | if not chunk and written then break end | |
287 | end | |
288 | ||
289 | pasvSock.close() | |
290 | end | |
291 | ||
292 | local function writePasv(pasvSock, f) | |
293 | repeat | |
294 | local chunk, len = f() | |
295 | if chunk then | |
296 | len = len or 0 | |
297 | local written = 0 | |
298 | repeat | |
299 | written = written + pasvSock.write(chunk) | |
300 | until written >= len | |
301 | end | |
302 | until not chunk | |
303 | ||
304 | pasvSock.write("") | |
305 | pasvSock.close() | |
306 | ||
307 | os.sleep(0.2) | |
308 | end | |
309 | ||
310 | local function handleInput(str) | |
311 | str = text.trim(str) | |
312 | ||
313 | if str:sub(1, 1) == "!" then | |
314 | if str:sub(2, -1) == "" then | |
315 | shell.execute("sh") | |
316 | else | |
317 | shell.execute(str:sub(2, -1)) | |
318 | end | |
319 | ||
320 | return | |
321 | end | |
322 | ||
323 | local cmd, args do | |
324 | local tokens = text.tokenize(str) | |
325 | if #tokens < 1 then return end | |
326 | ||
327 | cmd = tokens[1] | |
328 | table.remove(tokens, 1) | |
329 | args = tokens | |
330 | end | |
331 | ||
332 | if commands[cmd] then | |
333 | commands[cmd](args) | |
334 | else | |
335 | setFG(0xFF0000) | |
336 | print("Invalid command.") | |
337 | setFG(0xFFFFFF) | |
338 | end | |
339 | end | |
340 | ||
341 | local function main() | |
342 | connect() | |
343 | auth() | |
344 | ||
345 | repeat | |
346 | setFG(0x999999) | |
347 | io.write("ftp> ") | |
348 | setFG(0xFFFFFF) | |
349 | local input = term.read(history) | |
350 | ||
351 | if input and text.trim(input) ~= "" then | |
352 | handleInput(input) | |
353 | ||
354 | if input:sub(1, 1) ~= " " then | |
355 | table.insert(history, input) | |
356 | end | |
357 | end | |
358 | until not input | |
359 | print() | |
360 | ||
361 | isRunning = false | |
362 | end | |
363 | ||
364 | local progress do | |
365 | local units = {"B", "k", "M", "G", "T"} | |
366 | local chars = {[0] = ""} | |
367 | for i = 0x258f, 0x2588, -1 do | |
368 | table.insert(chars, unicode.char(i)) | |
369 | end | |
370 | ||
371 | local function formatFSize(fsize) | |
372 | local result = "" | |
373 | if fsize < 10e3 then | |
374 | result = ("%.4f"):format(fsize):sub(1,5) | |
375 | result = result .. (" "):rep(6 - #result) .. units[1] | |
376 | elseif fsize < 10e13 then | |
377 | local digits = #("%d"):format(fsize) | |
378 | local unit = units[math.floor((digits - 1) / 3) + 1] | |
379 | result = ("%.13f"):format(fsize / (10 ^ (math.floor((digits - 1) / 3) * 3))):sub(1, 5) | |
380 | result = result .. (" "):rep(6 - #result) .. unit | |
381 | else | |
382 | result = ("%.1e"):format(fsize) | |
383 | end | |
384 | return result | |
385 | end | |
386 | ||
387 | function progress(fgot, ftotal, tstart, width) | |
388 | local perc = 0 | |
389 | if tonumber(tostring(fgot / ftotal)) then | |
390 | perc = fgot / ftotal * 100 | |
391 | if perc > 100 then | |
392 | perc = 100 | |
393 | end | |
394 | else | |
395 | error("The programmer has derped! fgot = 0, ftotal = 0, perc = -nan, undefined behaviour!") | |
396 | end | |
397 | local pstr = ("%.2f"):format(perc) | |
398 | ||
399 | local delta = computer.uptime() - tstart | |
400 | local perperc = delta / perc | |
401 | local t = (100 - perc) * perperc | |
402 | local time = "n/a" | |
403 | local cspeed = "" | |
404 | if tonumber(tostring(t)) then | |
405 | cspeed = tonumber(tostring(fgot / delta)) | |
406 | cspeed = cspeed and (formatFSize(cspeed) .. "/s") or "n/a" | |
407 | local days = math.floor(t / 86400) | |
408 | t = t - days * 86400 | |
409 | local hours = math.floor(t / 3600) | |
410 | t = t - hours * 3600 | |
411 | local minutes = math.floor(t / 60) | |
412 | local seconds = t - minutes * 60 | |
413 | time = ("%02d"):format(seconds) | |
414 | time = ("%02d"):format(minutes) .. ":" .. time | |
415 | if hours ~= 0 or days ~= 0 then | |
416 | time = ("%02d"):format(hours) .. ":" .. time | |
417 | end | |
418 | if days ~= 0 then | |
419 | time = tostring(days) .. "d, " .. time | |
420 | end | |
421 | end | |
422 | ||
423 | local lpart = formatFSize(fgot) .. " / " .. formatFSize(ftotal) .. "β" | |
424 | local rpart = chars[1] .. (" "):rep(6 - #pstr) .. pstr .. "% " .. cspeed .. " -- " .. time | |
425 | local pwidth = width - unicode.len(lpart) - unicode.len(rpart) | |
426 | ||
427 | local cwidth = pwidth / 100 | |
428 | local fr = tonumber(select(2, math.modf(perc * cwidth))) | |
429 | local lastblockindex = math.floor(fr * 8) | |
430 | if lastblockindex == 8 then | |
431 | lastblockindex = 7 | |
432 | end | |
433 | local lastblock = chars[lastblockindex] | |
434 | local blocks = math.floor(perc * cwidth) | |
435 | ||
436 | local result = lpart .. chars[8]:rep(blocks) .. lastblock | |
437 | result = result .. (" "):rep(width - unicode.len(result) - unicode.len(rpart)) .. rpart | |
438 | return result | |
439 | end | |
440 | end | |
441 | ||
442 | -- Π‘ommands -------------------------------------------------------------------- | |
443 | ||
444 | function commands.quit() | |
445 | exit() | |
446 | end | |
447 | ||
448 | function commands.pwd(args) | |
449 | local _, copts = shell.parse(table.unpack(args)) | |
450 | if copts.help or copts.h then | |
451 | print("pwd - print working directory.") | |
452 | print() | |
453 | print("Usage: pwd [-h|--help]") | |
454 | print() | |
455 | print("Options:") | |
456 | print(" -h|--help Print this message") | |
457 | ||
458 | return | |
459 | end | |
460 | ||
461 | sock:write("PWD\r\n") | |
462 | sock:flush() | |
463 | read(trace) | |
464 | end | |
465 | ||
466 | function commands.system(args) | |
467 | local _, copts = shell.parse(table.unpack(args)) | |
468 | if copts.help or copts.h then | |
469 | print("system - print system running on server.") | |
470 | print() | |
471 | print("Usage: system [-h|--help]") | |
472 | print() | |
473 | print("Options:") | |
474 | print(" -h|--help Print this message") | |
475 | ||
476 | return | |
477 | end | |
478 | ||
479 | sock:write("SYST\r\n") | |
480 | sock:flush() | |
481 | read(trace) | |
482 | end | |
483 | ||
484 | function commands.nop(args) | |
485 | local _, copts = shell.parse(table.unpack(args)) | |
486 | if copts.help or copts.h then | |
487 | print("nop - do nothing.") | |
488 | print() | |
489 | print("Usage: system [-h|--help]") | |
490 | print() | |
491 | print("Options:") | |
492 | print(" -h|--help Print this message") | |
493 | ||
494 | return | |
495 | end | |
496 | ||
497 | sock:write("NOOP\r\n") | |
498 | sock:flush() | |
499 | read(trace) | |
500 | end | |
501 | ||
502 | commands[".."] = function (args) | |
503 | local _, copts = shell.parse(table.unpack(args)) | |
504 | if copts.help or copts.h then | |
505 | print(".. - go to the parent directory.") | |
506 | print() | |
507 | print("Usage: .. [-h|--help]") | |
508 | print("Options:") | |
509 | print(" -h|--help Print this message") | |
510 | ||
511 | return | |
512 | end | |
513 | ||
514 | sock:write("CDUP\r\n") | |
515 | sock:flush() | |
516 | read(trace) | |
517 | end | |
518 | ||
519 | function commands.size(args) | |
520 | local _, copts = shell.parse(table.unpack(args)) | |
521 | if copts.help or copts.h then | |
522 | print("size - print file size.") | |
523 | print() | |
524 | print("Usage: size [-h|--help] <path>") | |
525 | print() | |
526 | print("Options:") | |
527 | print(" -h|--help Print this message") | |
528 | print() | |
529 | print("Arguments:") | |
530 | print(" path Path to file.") | |
531 | ||
532 | return | |
533 | end | |
534 | ||
535 | if #args < 1 then | |
536 | print("Usage: size <file>") | |
537 | return | |
538 | end | |
539 | ||
540 | sock:write("SIZE " .. args[1] .. "\r\n") | |
541 | sock:flush() | |
542 | read(trace) | |
543 | end | |
544 | ||
545 | function commands.shelp(args) | |
546 | local _, copts = shell.parse(table.unpack(args)) | |
547 | if copts.help or copts.h then | |
548 | print("shelp - print server help.") | |
549 | print() | |
550 | print("Usage: shelp [-h|--help]") | |
551 | print() | |
552 | print("Options:") | |
553 | print(" -h|--help Print this message") | |
554 | ||
555 | return | |
556 | end | |
557 | ||
558 | sock:write("HELP\r\n") | |
559 | sock:flush() | |
560 | read(trace) | |
561 | end | |
562 | ||
563 | function commands.stat(args) | |
564 | local _, copts = shell.parse(table.unpack(args)) | |
565 | if copts.help or copts.h then | |
566 | print("stat - print server statistics.") | |
567 | print() | |
568 | print("Usage: stat [-h|--help]") | |
569 | print() | |
570 | print("Options:") | |
571 | print(" -h|--help Print this message") | |
572 | ||
573 | return | |
574 | end | |
575 | ||
576 | sock:write("STAT\r\n") | |
577 | sock:flush() | |
578 | read(trace) | |
579 | end | |
580 | ||
581 | function commands.binary(args) | |
582 | local _, copts = shell.parse(table.unpack(args)) | |
583 | if copts.help or copts.h then | |
584 | print("binary - switch to binary mode.") | |
585 | print() | |
586 | print("Usage: binary [-h|--help]") | |
587 | print() | |
588 | print("Options:") | |
589 | print(" -h|--help Print this message") | |
590 | ||
591 | return | |
592 | end | |
593 | ||
594 | sock:write("TYPE I\r\n") | |
595 | sock:flush() | |
596 | read(trace) | |
597 | end | |
598 | ||
599 | function commands.ascii() | |
600 | local _, copts = shell.parse(table.unpack(args)) | |
601 | if copts.help or copts.h then | |
602 | print("ascii - switch to ASCII mode.") | |
603 | print() | |
604 | print("Usage: ascii [-h|--help]") | |
605 | print() | |
606 | print("Options:") | |
607 | print(" -h|--help Print this message") | |
608 | ||
609 | return | |
610 | end | |
611 | ||
612 | sock:write("TYPE A\r\n") | |
613 | sock:flush() | |
614 | read(trace) | |
615 | end | |
616 | ||
617 | function commands.cd(args) | |
618 | local cargs, copts = shell.parse(table.unpack(args)) | |
619 | if copts.help or copts.h or #cargs < 1 then | |
620 | print("ascii - change working directory.") | |
621 | print() | |
622 | print("Usage: ascii [-h|--help] <path>") | |
623 | print() | |
624 | print("Options:") | |
625 | print(" -h|--help Print this message") | |
626 | print() | |
627 | print("Arguments:") | |
628 | print(" path Path to new working directory") | |
629 | ||
630 | return | |
631 | end | |
632 | ||
633 | sock:write("CWD " .. cargs[1] .. "\r\n") | |
634 | sock:flush() | |
635 | read(trace) | |
636 | end | |
637 | ||
638 | function commands.ls(args) | |
639 | local cargs, copts = shell.parse(table.unpack(args)) | |
640 | if copts.help or copts.h then | |
641 | print("ls - print list files.") | |
642 | print() | |
643 | print("Usage: ls [-h|--help] [path]") | |
644 | print() | |
645 | print("Options:") | |
646 | print(" -h|--help Print this message") | |
647 | print(" -s Use short listing format") | |
648 | print() | |
649 | print("Arguments:") | |
650 | print(" path Path to the directory (the current") | |
651 | print(" directory by default) to list its files") | |
652 | ||
653 | return | |
654 | end | |
655 | ||
656 | local pasvSock = pasv() | |
657 | if not pasvSock then | |
658 | return | |
659 | end | |
660 | ||
661 | sock:write((copts.s and "NLST" or "LIST") | |
662 | .. (cargs[1] and " " .. cargs[1] or "") .. "\r\n") | |
663 | sock:flush() | |
664 | read(function (str) | |
665 | local data = trace(str) | |
666 | if data and data.code == 150 and data.isLast then | |
667 | readPasv(pasvSock, function (str) print(str) end) | |
668 | end | |
669 | end) | |
670 | end | |
671 | ||
672 | function commands.rename(args) | |
673 | local cargs, copts = shell.parse(table.unpack(args)) | |
674 | if copts.help or copts.h or #cargs < 2 then | |
675 | print("rename - rename file or directory.") | |
676 | print() | |
677 | print("Usage: rename [-h|--help] <source> <dest>") | |
678 | print() | |
679 | print("Options:") | |
680 | print(" -h|--help Print this message") | |
681 | print() | |
682 | print("Arguments:") | |
683 | print(" source A path to file to rename") | |
684 | print(" dest A name the source should be renamed to") | |
685 | print() | |
686 | print("Aliases: rn") | |
687 | ||
688 | return | |
689 | end | |
690 | ||
691 | sock:write("RNFR " .. cargs[1] .. "\r\n") | |
692 | sock:flush() | |
693 | read(function (str) | |
694 | local data = trace(str) | |
695 | if data and data.code == 350 and data.isLast then | |
696 | sock:write("RNTO " .. cargs[2] .. "\r\n") | |
697 | sock:flush() | |
698 | read(trace) | |
699 | end | |
700 | end) | |
701 | end | |
702 | ||
703 | commands.rn = commands.rename | |
704 | ||
705 | function commands.rm(args) | |
706 | local cargs, copts = shell.parse(table.unpack(args)) | |
707 | if copts.help or copts.h or #cargs < 1 then | |
708 | print("rm - remove a file or directory.") | |
709 | print() | |
710 | print("Usage: rm [-h|--help] [-d] <path>") | |
711 | print() | |
712 | print("Options:") | |
713 | print(" -h|--help Print this message") | |
714 | print(" -d Remove a directory") | |
715 | print() | |
716 | print("Arguments:") | |
717 | print(" path A path to the file or directory to remove") | |
718 | ||
719 | return | |
720 | end | |
721 | ||
722 | sock:write((copts.d and "RMD " or "DELE ") .. cargs[1] .. "\r\n") | |
723 | sock:flush() | |
724 | read(trace) | |
725 | end | |
726 | ||
727 | function commands.mkdir(args) | |
728 | local cargs, copts = shell.parse(table.unpack(args)) | |
729 | if copts.help or copts.h or #cargs < 1 then | |
730 | print("mkdir - create a directory.") | |
731 | print() | |
732 | print("Usage: mkdir [-h|--help] <path>") | |
733 | print() | |
734 | print("Options:") | |
735 | print(" -h|--help Print this message") | |
736 | print() | |
737 | print("Arguments:") | |
738 | print(" path A path to new directory") | |
739 | ||
740 | return | |
741 | end | |
742 | ||
743 | sock:write("MKD " .. cargs[1] .. "\r\n") | |
744 | sock:flush() | |
745 | read(trace) | |
746 | end | |
747 | ||
748 | function commands.raw(args) | |
749 | local cargs, copts = shell.parse(table.unpack(args)) | |
750 | if copts.help or copts.h or #cargs < 1 then | |
751 | print("raw - send a message to the server.") | |
752 | print("Sent message will end with CRLF (\\r\\n)") | |
753 | print() | |
754 | print("Usage: raw [-h|--help] <message>") | |
755 | print() | |
756 | print("Options:") | |
757 | print(" -h|--help Print this message") | |
758 | print() | |
759 | print("Arguments:") | |
760 | print(" message Message that should be sent to the server") | |
761 | ||
762 | return | |
763 | end | |
764 | ||
765 | sock:write(cargs[1] .. "\r\n") | |
766 | sock:flush() | |
767 | os.sleep(0.5) | |
768 | read(trace, true) | |
769 | end | |
770 | ||
771 | function commands.get(args) | |
772 | local cargs, copts = shell.parse(table.unpack(args)) | |
773 | if copts.help or copts.h or #cargs < 1 then | |
774 | print("get - get a file from the server.") | |
775 | print() | |
776 | print("Usage: get [-h|--help] <remote-path> [local-path]") | |
777 | print() | |
778 | print("Options:") | |
779 | print(" -h|--help Print this message") | |
780 | print() | |
781 | print("Arguments:") | |
782 | print(" remote-path A path to file that should be got from the server") | |
783 | print(" local-path A path to local file (remote-path by default)") | |
784 | ||
785 | return | |
786 | end | |
787 | ||
788 | local file, reason = io.open(cargs[2] or cargs[1], "w") | |
789 | ||
790 | if not file then | |
791 | setFG(0xFF0000) | |
792 | print("Error opening file for writing: " | |
793 | .. tostring(reason or "unknown error")) | |
794 | setFG(0x000000) | |
795 | return | |
796 | end | |
797 | ||
798 | sock:write("SIZE " .. cargs[1] .. "\r\n") | |
799 | sock:flush() | |
800 | ||
801 | local size = 0 | |
802 | read(function (str) | |
803 | local data = trace(str) | |
804 | if data and data.code == 213 and data.isLast then | |
805 | size = data.text:match("(%d+)") | |
806 | end | |
807 | end) | |
808 | size = tonumber(size) | |
809 | ||
810 | local pasvSock = pasv() | |
811 | if not pasvSock then | |
812 | return | |
813 | end | |
814 | ||
815 | sock:write("RETR " .. cargs[1] .. "\r\n") | |
816 | sock:flush() | |
817 | read(function (str) | |
818 | local data = trace(str) | |
819 | if data and data.code == 150 and data.isLast then | |
820 | local start = computer.uptime() | |
821 | local show = size and size > 0 | |
822 | local now = 0 | |
823 | ||
824 | io.write(progress(now, size, start, w)) | |
825 | ||
826 | readPasv(pasvSock, function (chunk, len) | |
827 | file:write(chunk) | |
828 | if show then | |
829 | now = now + len | |
830 | term.clearLine() | |
831 | io.write(progress(now, size, start, w)) | |
832 | end | |
833 | end) | |
834 | ||
835 | if show then | |
836 | print() | |
837 | end | |
838 | ||
839 | file:flush() | |
840 | file:close() | |
841 | end | |
842 | end) | |
843 | end | |
844 | ||
845 | function commands.put(args) | |
846 | local cargs, copts = shell.parse(table.unpack(args)) | |
847 | if copts.help or copts.h or #cargs < 1 then | |
848 | print("put - put a file to the server.") | |
849 | print() | |
850 | print("Usage: put [-h|--help] <local-path> [remote-path]") | |
851 | print() | |
852 | print("Options:") | |
853 | print(" -h|--help Print this message") | |
854 | print() | |
855 | print("Arguments:") | |
856 | print(" local-path A path to local file.") | |
857 | print(" remote-path A path to file that should be put to the server") | |
858 | print(" (local-path by default)") | |
859 | ||
860 | return | |
861 | end | |
862 | ||
863 | local file, reason = io.open(cargs[1], "rb") | |
864 | ||
865 | if not file then | |
866 | setFG(0xFF0000) | |
867 | print("Error opening file for reading: " | |
868 | .. tostring(reason or "unknown error")) | |
869 | setFG(0x000000) | |
870 | return | |
871 | end | |
872 | ||
873 | local pasvSock = pasv() | |
874 | if not pasvSock then | |
875 | return | |
876 | end | |
877 | ||
878 | sock:write("STOR " .. (cargs[2] or cargs[1]) .. "\r\n") | |
879 | sock:flush() | |
880 | read(function (str) | |
881 | local data = trace(str) | |
882 | if data and data.code == 150 and data.isLast then | |
883 | os.sleep(0.3) | |
884 | ||
885 | local start = computer.uptime() | |
886 | local size = fs.size(os.getenv("PWD") .. "/" .. cargs[1]) | |
887 | local now = 0 | |
888 | ||
889 | io.write(progress(now, size, start, w)) | |
890 | ||
891 | writePasv(pasvSock, function () | |
892 | local chunk = file:read(chsize) | |
893 | if not chunk then | |
894 | return | |
895 | end | |
896 | ||
897 | local len = #chunk | |
898 | ||
899 | now = now + len | |
900 | term.clearLine() | |
901 | io.write(progress(now, size, start, w)) | |
902 | ||
903 | return chunk, len | |
904 | end) | |
905 | ||
906 | print() | |
907 | ||
908 | file:close() | |
909 | end | |
910 | end) | |
911 | end | |
912 | ||
913 | ||
914 | -- Main ------------------------------------------------------------------------ | |
915 | ||
916 | init(...) | |
917 | main() | |
918 | exit() |