Advertisement
Ewgeniy

Tape

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