--[[ tape program, provides basic tape modification and access tools Authors: Bizzycola and Vexatos ]] local component = require("component") local fs = require("filesystem") local shell = require("shell") local term = require("term") local args, options = shell.parse(...) if not component.isAvailable("tape_drive") then io.stderr:write("This program requires a tape drive to run.") return end local function printUsage() print("Usage:") print(" - 'tape play' to start playing a tape") print(" - 'tape pause' to pause playing the tape") print(" - 'tape stop' to stop playing and rewind the tape") print(" - 'tape rewind' to rewind the tape") print(" - 'tape wipe' to wipe any data on the tape and erase it completely") print(" - 'tape label [name]' to label the tape, leave 'name' empty to get current label") print(" - 'tape speed <speed>' to set the playback speed. Needs to be between 0.25 and 2.0") print(" - 'tape volume <volume>' to set the volume of the tape. Needs to be between 0.0 and 1.0") print(" - 'tape write <path/of/audio/file>' to write to the tape from a file") print(" - 'tape write <URL>' to write from a URL") print("Other options:") print(" '--address=<address>' to use a specific tape drive") print(" '--b=<bytes>' to specify the size of the chunks the program will write to a tape") print(" '--t=<timeout>' to specify a custom maximum timeout in seconds when writing from a URL") print(" '-y' to not ask for confirmation before starting to write") return end local function getTapeDrive() --Credits to gamax92 for this local tape if options.address then if type(options.address) ~= "string" then io.stderr:write("'address' may only be a string.") return end local fulladdr = component.get(options.address) if fulladdr == nil then io.stderr:write("No component at this address.") return end if component.type(fulladdr) ~= "tape_drive" then io.stderr:write("No tape drive at this address.") return end tape = component.proxy(fulladdr) else tape = component.tape_drive end return tape --End of gamax92's part end local tape = getTapeDrive() if not tape.isReady() then io.stderr:write("The tape drive does not contain a tape.") return end local function label(name) if not name then if tape.getLabel() == "" then print("Tape is currently not labeled.") return end print("Tape is currently labeled: " .. tape.getLabel()) return end tape.setLabel(name) print("Tape label set to " .. name) end local function rewind() print("Rewound tape") tape.seek(-tape.getSize()) end local function play() if tape.getState() == "PLAYING" then print("Tape is already playing") else tape.play() print("Tape started") end end local function stop() if tape.getState() == "STOPPED" then print("Tape is already stopped") else tape.stop() tape.seek(-tape.getSize()) print("Tape stopped") end end local function pause() if tape.getState() == "STOPPED" then print("Tape is already paused") else tape.stop() print("Tape paused") end end local function speed(sp) local s = tonumber(sp) if not s or s < 0.25 or s > 2 then io.stderr:write("Speed needs to be a number between 0.25 and 2.0") return end tape.setSpeed(s) print("Playback speed set to " .. sp) end local function volume(vol) local v = tonumber(vol) if not v or v < 0 or v > 1 then io.stderr:write("Volume needs to be a number between 0.0 and 1.0") return end tape.setVolume(v) print("Volume set to " .. vol) end local function confirm(msg) if not options.y then print(msg) print("Type `y` to confirm, `n` to cancel.") repeat local response = io.read() if response and response:lower():sub(1, 1) == "n" then print("Canceled.") return false end until response and response:lower():sub(1, 1) == "y" end return true end local function wipe() if not confirm("Are you sure you want to wipe this tape?") then return end local k = tape.getSize() tape.stop() tape.seek(-k) tape.stop() --Just making sure tape.seek(-90000) local s = string.rep("\xAA", 8192) for i = 1, k + 8191, 8192 do tape.write(s) end tape.seek(-k) tape.seek(-90000) print("Done.") end local function writeTape(path) local file, msg, _, y local block = 2048 --How much to read at a time if options.b then local nBlock = tonumber(options.b) if nBlock then print("Setting chunk size to " .. options.b) block = nBlock else io.stderr:write("option --b is not a number.\n") return end end if not confirm("Are you sure you want to write to this tape?") then return end tape.stop() tape.seek(-tape.getSize()) tape.stop() --Just making sure local bytery = 0 --For the progress indicator local filesize = tape.getSize() if string.match(path, "https?://.+") then if not component.isAvailable("internet") then io.stderr:write("This command requires an internet card to run.") return false end local internet = component.internet local function setupConnection(url) local file, reason = internet.request(url) if not file then io.stderr:write("error requesting data from URL: " .. reason .. "\n") return false end local connected, reason = false, "" local timeout = 50 if options.t then local nTimeout = tonumber(options.t) if nTimeout then print("Max timeout: " .. options.t) timeout = nTimeout * 10 else io.stderr:write("option --t is not a number. Defaulting to 5 seconds.\n") end end for i = 1, timeout do connected, reason = file.finishConnect() os.sleep(.1) if connected or connected == nil then break end end if connected == nil then io.stderr:write("Could not connect to server: " .. reason) return false end local status, message, header = file.response() if status then status = string.format("%d", status) print("Status: " .. status .. " " .. message) if status:sub(1,1) == "2" then return true, { close = function(self, ...) return file.close(...) end, read = function(self, ...) return file.read(...) end, }, header end return false end io.stderr:write("no valid HTTP response - no response") return false end local success, header success, file, header = setupConnection(path) if not success then if file then file:close() end return end print("Writing...") _, y = term.getCursor() if header and header["Content-Length"] and header["Content-Length"][1] then filesize = tonumber(header["Content-Length"][1]) end else local path = shell.resolve(path) filesize = fs.size(path) print("Path: " .. path) file, msg = io.open(path, "rb") if not file then io.stderr:write("Error: " .. msg) return end print("Writing...") _, y = term.getCursor() end if filesize > tape.getSize() then term.setCursor(1, y) io.stderr:write("Warning: File is too large for tape, shortening file\n") _, y = term.getCursor() filesize = tape.getSize() end --Displays long numbers with commas local function fancyNumber(n) return tostring(math.floor(n)):reverse():gsub("(%d%d%d)", "%1,"):gsub("%D$", ""):reverse() end repeat local bytes = file:read(block) if bytes and #bytes > 0 then if not tape.isReady() then io.stderr:write("\nError: Tape was removed during writing.\n") file:close() return end term.setCursor(1, y) bytery = bytery + #bytes local displaySize = math.min(bytery, filesize) term.write(string.format("Read %s of %s bytes... (%.2f %%)", fancyNumber(displaySize), fancyNumber(filesize), 100 * displaySize / filesize)) tape.write(bytes) end until not bytes or bytery > filesize file:close() tape.stop() tape.seek(-tape.getSize()) tape.stop() --Just making sure print("\nDone.") end if args[1] == "play" then play() elseif args[1] == "stop" then stop() elseif args[1] == "pause" then pause() elseif args[1] == "rewind" then rewind() elseif args[1] == "label" then label(args[2]) elseif args[1] == "speed" then speed(args[2]) elseif args[1] == "volume" then volume(args[2]) elseif args[1] == "write" then writeTape(args[2]) elseif args[1] == "wipe" then wipe() else printUsage() end