Advertisement
Guest User

tape.lua

a guest
Jun 29th, 2020
148
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.34 KB | None | 0 0
  1. --[[ tape program, provides basic tape modification and access tools
  2. Authors: Bizzycola and Vexatos
  3. ]]
  4. local component = require("component")
  5. local fs = require("filesystem")
  6. local shell = require("shell")
  7. local term = require("term")
  8.  
  9. local args, options = shell.parse(...)
  10.  
  11. if not component.isAvailable("tape_drive") then
  12.   io.stderr:write("This program requires a tape drive to run.")
  13.   return
  14. end
  15.  
  16. local function printUsage()
  17.   print("Usage:")
  18.   print(" - 'tape play' to start playing a tape")
  19.   print(" - 'tape pause' to pause playing the tape")
  20.   print(" - 'tape stop' to stop playing and rewind the tape")
  21.   print(" - 'tape rewind' to rewind the tape")
  22.   print(" - 'tape wipe' to wipe any data on the tape and erase it completely")
  23.   print(" - 'tape label [name]' to label the tape, leave 'name' empty to get current label")
  24.   print(" - 'tape speed <speed>' to set the playback speed. Needs to be between 0.25 and 2.0")
  25.   print(" - 'tape volume <volume>' to set the volume of the tape. Needs to be between 0.0 and 1.0")
  26.   print(" - 'tape write <path/of/audio/file>' to write to the tape from a file")
  27.   print(" - 'tape write <URL>' to write from a URL")
  28.   print(" - 'tape read <path/of/save/file> to read data from the tape")
  29.   print("Other options:")
  30.   print(" '--address=<address>' to use a specific tape drive")
  31.   print(" '--b=<bytes>' to specify the size of the chunks the program will write to a tape")
  32.   print(" '--t=<timeout>' to specify a custom maximum timeout in seconds when writing from a URL")
  33.   print(" '-y' to not ask for confirmation before starting to write")
  34.   return
  35. end
  36.  
  37. local function getTapeDrive()
  38.   --Credits to gamax92 for this
  39.   local tape
  40.   if options.address then
  41.     if type(options.address) ~= "string" then
  42.       io.stderr:write("'address' may only be a string.")
  43.       return
  44.     end
  45.     local fulladdr = component.get(options.address)
  46.     if fulladdr == nil then
  47.       io.stderr:write("No component at this address.")
  48.       return
  49.     end
  50.     if component.type(fulladdr) ~= "tape_drive" then
  51.       io.stderr:write("No tape drive at this address.")
  52.       return
  53.     end
  54.     tape = component.proxy(fulladdr)
  55.   else
  56.     tape = component.tape_drive
  57.   end
  58.   return tape
  59.   --End of gamax92's part
  60. end
  61.  
  62. local tape = getTapeDrive()
  63.  
  64. if not tape.isReady() then
  65.   io.stderr:write("The tape drive does not contain a tape.")
  66.   return
  67. end
  68.  
  69. local function label(name)
  70.   if not name then
  71.     if tape.getLabel() == "" then
  72.       print("Tape is currently not labeled.")
  73.       return
  74.     end
  75.     print("Tape is currently labeled: " .. tape.getLabel())
  76.     return
  77.   end
  78.   tape.setLabel(name)
  79.   print("Tape label set to " .. name)
  80. end
  81.  
  82. local function rewind()
  83.   print("Rewound tape")
  84.   tape.seek(-tape.getSize())
  85. end
  86.  
  87. local function play()
  88.   if tape.getState() == "PLAYING" then
  89.     print("Tape is already playing")
  90.   else
  91.     tape.play()
  92.     print("Tape started")
  93.   end
  94. end
  95.  
  96. local function stop()
  97.   if tape.getState() == "STOPPED" then
  98.     print("Tape is already stopped")
  99.   else
  100.     tape.stop()
  101.     tape.seek(-tape.getSize())
  102.     print("Tape stopped")
  103.   end
  104. end
  105.  
  106. local function pause()
  107.   if tape.getState() == "STOPPED" then
  108.     print("Tape is already paused")
  109.   else
  110.     tape.stop()
  111.     print("Tape paused")
  112.   end
  113. end
  114.  
  115. local function speed(sp)
  116.   local s = tonumber(sp)
  117.   if not s or s < 0.25 or s > 2 then
  118.     io.stderr:write("Speed needs to be a number between 0.25 and 2.0")
  119.     return
  120.   end
  121.   tape.setSpeed(s)
  122.   print("Playback speed set to " .. sp)
  123. end
  124.  
  125. local function volume(vol)
  126.   local v = tonumber(vol)
  127.   if not v or v < 0 or v > 1 then
  128.     io.stderr:write("Volume needs to be a number between 0.0 and 1.0")
  129.     return
  130.   end
  131.   tape.setVolume(v)
  132.   print("Volume set to " .. vol)
  133. end
  134.  
  135. local function confirm(msg)
  136.   if not options.y then
  137.     print(msg)
  138.     print("Type `y` to confirm, `n` to cancel.")
  139.     repeat
  140.       local response = io.read()
  141.       if response and response:lower():sub(1, 1) == "n" then
  142.         print("Canceled.")
  143.         return false
  144.       end
  145.     until response and response:lower():sub(1, 1) == "y"
  146.   end
  147.   return true
  148. end
  149.  
  150. local function wipe()
  151.   if not confirm("Are you sure you want to wipe this tape?") then return end
  152.   local k = tape.getSize()
  153.   tape.stop()
  154.   tape.seek(-k)
  155.   tape.stop() --Just making sure
  156.   tape.seek(-90000)
  157.   local s = string.rep("\xAA", 8192)
  158.   for i = 1, k + 8191, 8192 do
  159.     tape.write(s)
  160.   end
  161.   tape.seek(-k)
  162.   tape.seek(-90000)
  163.   print("Done.")
  164. end
  165.  
  166. local function readTape(path)
  167.   local _, y = term.getCursor()
  168.   local file = io.open(path, "w")
  169.   if not file then print("Can't open " .. path) return end
  170.  
  171.   local block = options.b and tonumber(options.b) or 2048
  172.   if block <= 0 then block = 2048 end
  173.   if not confirm("Are you sure you want to write to " .. path .. " ?") then
  174.     file:close() return
  175.   end
  176.   tape.stop()
  177.   tape.seek(-tape.getSize())
  178.   tape.stop()
  179.  
  180.   for i = 1, tape.getSize(), block do
  181.     local d = tape.read(block):gsub("\0", "")
  182.     if #d == 0 then break end
  183.     file:write(d)
  184.     term.setCursor(1, y + 5)
  185.     print('Write ' .. tostring(i) .. ' bytes of ' .. tape.getSize())
  186.   end
  187.   file:close()
  188.   print("Data transferred successfully!")
  189. end
  190.  
  191. local function writeTape(path)
  192.   local file, msg, _, y
  193.   local block = 2048 --How much to read at a time
  194.   if options.b then
  195.     local nBlock = tonumber(options.b)
  196.     if nBlock then
  197.       print("Setting chunk size to " .. options.b)
  198.       block = nBlock
  199.     else
  200.       io.stderr:write("option --b is not a number.\n")
  201.       return
  202.     end
  203.   end
  204.   if not confirm("Are you sure you want to write to this tape?") then return end
  205.   tape.stop()
  206.   tape.seek(-tape.getSize())
  207.   tape.stop() --Just making sure
  208.  
  209.   local bytery = 0 --For the progress indicator
  210.   local filesize = tape.getSize()
  211.  
  212.   if string.match(path, "https?://.+") then
  213.  
  214.     if not component.isAvailable("internet") then
  215.       io.stderr:write("This command requires an internet card to run.")
  216.       return false
  217.     end
  218.  
  219.     local internet = component.internet
  220.  
  221.     local function setupConnection(url)
  222.  
  223.       local file, reason = internet.request(url)
  224.  
  225.       if not file then
  226.         io.stderr:write("error requesting data from URL: " .. reason .. "\n")
  227.         return false
  228.       end
  229.  
  230.       local connected, reason = false, ""
  231.       local timeout = 50
  232.       if options.t then
  233.         local nTimeout = tonumber(options.t)
  234.         if nTimeout then
  235.           print("Max timeout: " .. options.t)
  236.           timeout = nTimeout * 10
  237.         else
  238.           io.stderr:write("option --t is not a number. Defaulting to 5 seconds.\n")
  239.         end
  240.       end
  241.       for i = 1, timeout do
  242.         connected, reason = file.finishConnect()
  243.         os.sleep(.1)
  244.         if connected or connected == nil then
  245.           break
  246.         end
  247.       end
  248.      
  249.       if connected == nil then
  250.         io.stderr:write("Could not connect to server: " .. reason)
  251.         return false
  252.       end
  253.  
  254.       local status, message, header = file.response()
  255.  
  256.       if status then
  257.         status = string.format("%d", status)
  258.         print("Status: " .. status .. " " .. message)
  259.         if status:sub(1,1) == "2" then
  260.           return true, {
  261.             close = function(self, ...) return file.close(...) end,
  262.             read = function(self, ...) return file.read(...) end,
  263.           }, header
  264.         end
  265.         return false
  266.       end
  267.       io.stderr:write("no valid HTTP response - no response")
  268.       return false
  269.     end
  270.  
  271.     local success, header
  272.     success, file, header = setupConnection(path)
  273.     if not success then
  274.       if file then
  275.         file:close()
  276.       end
  277.       return
  278.     end
  279.  
  280.     print("Writing...")
  281.  
  282.     _, y = term.getCursor()
  283.  
  284.     if header and header["Content-Length"] and header["Content-Length"][1] then
  285.       filesize = tonumber(header["Content-Length"][1])
  286.     end
  287.   else
  288.     local path = shell.resolve(path)
  289.     filesize = fs.size(path)
  290.     print("Path: " .. path)
  291.     file, msg = io.open(path, "rb")
  292.     if not file then
  293.       io.stderr:write("Error: " .. msg)
  294.       return
  295.     end
  296.  
  297.     print("Writing...")
  298.  
  299.     _, y = term.getCursor()
  300.   end
  301.  
  302.   if filesize > tape.getSize() then
  303.     term.setCursor(1, y)
  304.     io.stderr:write("Warning: File is too large for tape, shortening file\n")
  305.     _, y = term.getCursor()
  306.     filesize = tape.getSize()
  307.   end
  308.  
  309.   --Displays long numbers with commas
  310.   local function fancyNumber(n)
  311.     return tostring(math.floor(n)):reverse():gsub("(%d%d%d)", "%1,"):gsub("%D$", ""):reverse()
  312.   end
  313.  
  314.   repeat
  315.     local bytes = file:read(block)
  316.     if bytes and #bytes > 0 then
  317.       if not tape.isReady() then
  318.         io.stderr:write("\nError: Tape was removed during writing.\n")
  319.         file:close()
  320.         return
  321.       end
  322.       term.setCursor(1, y)
  323.       bytery = bytery + #bytes
  324.       local displaySize = math.min(bytery, filesize)
  325.       term.write(string.format("Read %s of %s bytes... (%.2f %%)", fancyNumber(displaySize), fancyNumber(filesize), 100 * displaySize / filesize))
  326.       tape.write(bytes)
  327.     end
  328.   until not bytes or bytery > filesize
  329.   file:close()
  330.   tape.stop()
  331.   tape.seek(-tape.getSize())
  332.   tape.stop() --Just making sure
  333.   print("\nDone.")
  334. end
  335.  
  336. if args[1] == "play" then
  337.   play()
  338. elseif args[1] == "stop" then
  339.   stop()
  340. elseif args[1] == "pause" then
  341.   pause()
  342. elseif args[1] == "rewind" then
  343.   rewind()
  344. elseif args[1] == "label" then
  345.   label(args[2])
  346. elseif args[1] == "speed" then
  347.   speed(args[2])
  348. elseif args[1] == "volume" then
  349.   volume(args[2])
  350. elseif args[1] == "write" then
  351.   writeTape(args[2])
  352. elseif args[1] == "wipe" then
  353.   wipe()
  354. elseif args[1] == "read" then
  355.   readTape(args[2])
  356. else
  357.   printUsage()
  358. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement