Advertisement
TetraSource

Bundle

Sep 26th, 2018
993
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 22.85 KB | None | 0 0
  1. --[[
  2. Bundle API by TetraSource
  3. version v1.1
  4.  
  5. Merges multiple file systems that are attached to one computer or server
  6. into a virtual one.
  7.  
  8. dependencies:
  9.  none
  10. ]]--
  11.  
  12. ---------------
  13. -- constants --
  14. ---------------
  15.  
  16. -- adapts this to your hardware, OC configuration and OS.
  17.  
  18. -- Set this to true if you're running the script on a machine with
  19. -- case insensitive file system, e.g. the one of a windows machine.
  20. local CASE_INSENSITIVE = true
  21.  
  22. -- The maximal length of a string to be written or read from this file system.
  23. -- You should adapt it to the RAM capacity of your computer.
  24. local MAX_IO = 1024
  25.  
  26. -- The extra size each file takes (see OC config - fileCost).
  27. local FILE_COST = 512
  28.  
  29. -- The address of the file system component provided by this API.
  30. local ADDRESS = "ffffffff-ffff-ffff-ffff-ffffffffffff"
  31.  
  32. ---------------
  33. -- variables --
  34. ---------------
  35.  
  36. local component = require("component")
  37.  
  38. local currIndex = 1
  39. local filesystems = {}
  40.  
  41. local label = ""
  42. local spaceTotal = 0
  43. local structure = {}
  44. local handles = setmetatable({}, {__mode = "k"})
  45.  
  46. local cmp = {
  47.   slot = -1,
  48.   address = ADDRESS,
  49.   type = "filesystem",
  50. }
  51.  
  52. local otherAddress = component.list("filesystem", true)()
  53. local nativeComponent = {}
  54. local running = false
  55.  
  56. ----------------------
  57. -- helper functions --
  58. ----------------------
  59.  
  60. local function segments(path)
  61.   if CASE_INSENSITIVE then
  62.     path = path:lower()
  63.   end
  64.   local parts = {}
  65.   for part in path:gmatch("[^\\/]+") do
  66.     local current, up = part:find("^%.?%.$")
  67.     if current then
  68.       if up == 2 then
  69.         table.remove(parts)
  70.       end
  71.     else
  72.       table.insert(parts, part)
  73.     end
  74.   end
  75.   return parts
  76. end
  77.  
  78. local function findDir(path)
  79.   local parts = segments(path)
  80.   local start = structure
  81.   for i = 1, #parts-1 do
  82.     start = start[ parts[i] ]
  83.     if not start or start.isFile == true then
  84.       return nil
  85.     end
  86.   end
  87.   return table.concat(parts, '/', 1, #parts-1), start, parts[#parts], parts
  88. end
  89.  
  90. local function mark(pos)
  91.   local proxy = filesystems[pos]
  92.   local handle = proxy.open(".bundle_metadata", "w")
  93.   proxy.write(handle, string.format("%s%4x", ADDRESS, bit32.band(pos, 0xffff)))
  94.   proxy.close(handle)
  95. end
  96.  
  97. --------------------------------
  98. -- fake file system component --
  99. --------------------------------
  100.  
  101. function cmp.setLabel(newLabel)
  102.   checkArg(1, newLabel, "string")
  103.   if filesystems[1] then
  104.     label = filesystems[1].setLabel(newLabel)
  105.   end
  106.   return label
  107. end
  108.  
  109. function cmp.getLabel()
  110.   return label
  111. end
  112.  
  113. function cmp.isReadOnly()
  114.   return false
  115. end
  116.  
  117. function cmp.spaceUsed()
  118.   local used = 0
  119.   for i = 1, #filesystems do
  120.     used = used + filesystems[i].spaceUsed()
  121.   end
  122.   return used
  123. end
  124.  
  125. function cmp.spaceTotal()
  126.   return spaceTotal
  127. end
  128.  
  129. function cmp.size(path)
  130.   checkArg(1, path, "string")
  131.  
  132.   local file, name
  133.   path, file, name = findDir(path)
  134.   file = file and file[name]
  135.   if not file or file.isFile ~= true then
  136.     return 0
  137.   end
  138.   path = path .. "/" .. name
  139.  
  140.   local size = 0
  141.   for i = file.from, file.to do
  142.     size = size + filesystems[i].size(path)
  143.   end
  144.   return size
  145. end
  146.  
  147. function cmp.exists(path)
  148.   checkArg(1, path, "string")
  149.  
  150.   local parts = segments(path)
  151.   local start = structure
  152.   for i = 1, #parts do
  153.     if start.isFile == true then
  154.       return false
  155.     end
  156.     start = start[ parts[i] ]
  157.     if not start then
  158.       return false
  159.     end
  160.   end
  161.   return true
  162. end
  163.  
  164. function cmp.isDirectory(path)
  165.   checkArg(1, path, "string")
  166.  
  167.   local parts = segments(path)
  168.   local start = structure
  169.   for i = 1, #parts do
  170.     start = start[ parts[i] ]
  171.     if not start or start.isFile == true then
  172.       return false
  173.     end
  174.   end
  175.   return true
  176. end
  177.  
  178. function cmp.list(path)
  179.   checkArg(1, path, "string")
  180.  
  181.   local dir, name
  182.   path, dir, name = findDir(path)
  183.   if name and dir and dir.isFile ~= true then
  184.     dir = dir[name]
  185.   end
  186.   if not dir then
  187.     return nil, "no such file or directory"
  188.   elseif dir.isFile == true then
  189.     return {n = 1, [1] = name}
  190.   end
  191.  
  192.   local n, results = 0, {n = 0}
  193.   for member, element in next, dir do
  194.     n = n+1
  195.     results[n] = element.isFile == true and member or member .. '/'
  196.   end
  197.   results.n = n
  198.   return results -- is not sorted; no hidden files are hidden
  199. end
  200.  
  201. function cmp.makeDirectory(path)
  202.   checkArg(1, path, "string")
  203.  
  204.   if not filesystems[currIndex] then
  205.     return nil, "not enough space"
  206.   end
  207.  
  208.   local parts = segments(path)
  209.   local dir = structure
  210.   for i = 1, #parts do
  211.     local nextDir = dir[ parts[i] ]
  212.     if not nextDir then
  213.       if i == 1 and parts[1] == ".bundle_metadata" then
  214.         return false
  215.       end
  216.  
  217.       local ok, err = filesystems[currIndex].makeDirectory(path)
  218.       if not ok then
  219.         return ok, err
  220.       end
  221.       for j = i, #parts do
  222.         local new = {}
  223.         dir[ parts[j] ] = new
  224.         dir = new
  225.       end
  226.       return true
  227.     elseif nextDir.isFile == true then
  228.       return false
  229.     end
  230.     dir = nextDir
  231.   end
  232.   return false
  233. end
  234.  
  235. function cmp.rename(oldPath, newPath)
  236.   checkArg(1, oldPath, "string")
  237.   checkArg(2, newPath, "string")
  238.  
  239.   local oldDir, newDir, oldName, newName
  240.   oldPath, oldDir, oldName = findDir(oldPath)
  241.   newPath, newDir, newName = findDir(newPath)
  242.   if not (oldName and newName and oldDir and newDir and oldDir[oldName]) or
  243.    oldDir.isFile == true or newDir.isFile == true then
  244.     return false
  245.   end
  246.   if oldDir ~= newDir or oldName ~= newName then
  247.     if newDir[newName] then
  248.       return false
  249.     end
  250.     oldPath = oldPath .. "/" .. oldName
  251.     newPath = newPath .. "/" .. newName
  252.     local from = oldDir[oldName].isFile == true and oldDir[oldName].from or 1
  253.     for i = from,
  254.       oldDir[oldName].isFile == true and oldDir[oldName].to or #filesystems do
  255.       if not filesystems[i].rename(oldPath, newPath) then
  256.         -- undo renaming
  257.         for j = from, i-1 do
  258.           filesystems[j].rename(newPath, oldPath)
  259.         end
  260.         return false
  261.       end
  262.     end
  263.     newDir[newName] = oldDir[oldName]
  264.     oldDir[oldName] = nil
  265.   end
  266.   return true
  267. end
  268.  
  269. function cmp.remove(path)
  270.   checkArg(1, path, "string")
  271.  
  272.   local dir, name
  273.   path, dir, name = findDir(path)
  274.   if not dir or name and not dir[name] then
  275.     return false
  276.   end
  277.   local element = dir[name] or structure
  278.   local from = element.isFile == true and element.from or 1
  279.   local to = element.isFile == true and element.to or #filesystems
  280.   path = name and path .. "/" .. name or ""
  281.  
  282.   for handle, data in next, handles do
  283.     if data.path:sub(1, #path) == path then
  284.       return false
  285.     end
  286.   end
  287.   for i = from, to do
  288.     if not filesystems[i].remove(path) then
  289.       io.stderr:write("data is not entirely removed")
  290.       return false
  291.     end
  292.   end
  293.   if name then
  294.     dir[name] = nil
  295.   else
  296.     structure = {}
  297.     for i = from, to do
  298.       mark(i)
  299.     end
  300.   end
  301.   return true -- even if no file were removed
  302. end
  303.  
  304. local function writeOpenNext(data)
  305.   data.handle = nil
  306.   local fs = filesystems[data.index]
  307.   local i
  308.   while fs do
  309.     if currIndex < data.index then
  310.       currIndex = data.index
  311.     end
  312.  
  313.     -- create directory
  314.     i = #data.parts-1
  315.     if data.dir ~= "" then
  316.       while fs do
  317.         local dir = data.dir
  318.         while not fs.isDirectory(dir) do
  319.           i = i-1
  320.           dir = table.concat(parts, "/", 1, i)
  321.         end
  322.         if i+1 == #data.parts or fs.makeDirectory(data.dir) then
  323.           break
  324.         end
  325.         data.index = data.index+1
  326.         fs = filesystems[data.index]
  327.         if currIndex < data.index then
  328.           currIndex = data.index
  329.         end
  330.         i = #data.parts-1
  331.       end
  332.       if not fs then
  333.         break
  334.       end
  335.     end
  336.  
  337.     -- open / create file
  338.     local err
  339.     data.handle, err = fs.open(data.path, data.m)
  340.     if data.handle then
  341.       return nil
  342.     end
  343.     if i+1 < #data.parts then
  344.       fs.remove(table.concat(data.parts, '/', 1, i+1))
  345.     end
  346.     if err ~= "not enough space" then
  347.       return err
  348.     end
  349.     data.index = data.index+1
  350.     fs = filesystems[data.index]
  351.   end
  352.   return "not enough space"
  353. end
  354.  
  355. local function readOpenNext(data)
  356.   data.handle = nil
  357.   while data.index <= data.file.to do
  358.     data.handle = filesystems[data.index].open(data.path, data.m)
  359.     if data.handle then
  360.       return true
  361.     end
  362.     data.index = data.index+1
  363.   end
  364.   return false
  365. end
  366.  
  367. local modes = {
  368.   r = "r",
  369.   rb = "r",
  370.   w = "w",
  371.   wb = "w",
  372.   a = "a",
  373.   ab = "a",
  374. }
  375.  
  376. local resetFiles = true
  377.  
  378. function cmp.open(path, mode)
  379.   checkArg(1, path, "string")
  380.   checkArg(2, mode, "string", "nil")
  381.   if not mode then
  382.     mode = "r"
  383.   elseif not modes[mode] then
  384.     error("unsupported mode", 2)
  385.   end
  386.  
  387.   local dir, name, parts
  388.   path, dir, name, parts = findDir(path)
  389.   local m = mode
  390.   mode = modes[mode]
  391.  
  392.   -- no target
  393.   if not name or not dir[name] and mode == "r" or
  394.    -- target is a directory
  395.    dir[name] and dir[name].isFile ~= true or
  396.    -- target is a hidden path
  397.    path == "" and name == ".bundle_data" then
  398.     return nil, "cannot open file: " .. (name and path .. "/" .. name or "/")
  399.   elseif mode ~= "r" and not dir[name] and not filesystems[currIndex] then
  400.     return nil, "not enough space"
  401.   end
  402.  
  403.   local file = dir[name] or {
  404.     isFile = true,
  405.     from = currIndex,
  406.     to = currIndex,
  407.   }
  408.  
  409.   local data = {
  410.     parts = parts,
  411.     path = path .. "/" .. name,
  412.     dir = path,
  413.     m = m,
  414.     mode = mode,
  415.     file = file,
  416.     globalPos = 0,
  417.     localPos = 0,
  418.     index = mode ~= "a" and file.from or file.to,
  419.     handle = nil,
  420.     -- "local" variables
  421.     start = 0,
  422.   }
  423.  
  424.   path = data.path
  425.   if mode == "r" then
  426.     readOpenNext(data)
  427.   else
  428.     local err = writeOpenNext(data)
  429.     if err then
  430.       return nil, err
  431.     end
  432.  
  433.     if not dir[name] then
  434.       -- Set new file
  435.       dir[name] = file
  436.     elseif mode == "w" then
  437.       if resetFiles then
  438.         -- Remove all files of the file in when opening it in write mode
  439.         -- as this would clear the entire file on a file system, too.
  440.         local blocked = false
  441.         for handle, hData in next, handles do
  442.           if hData.path:sub(1, #path) == path then
  443.             if hData.mode ~= "r" then
  444.               blocked = true
  445.             elseif hData.handle then
  446.               filesystems[data.index].close(hData.handle)
  447.             end
  448.           end
  449.         end
  450.         if not blocked then
  451.           for i = file.from+1, file.to do
  452.             filesystems[i].remove(path)
  453.           end
  454.         end
  455.         for handle, hData in next, handles do
  456.           if hData.path:sub(1, #path) == path and hData.handle then
  457.             if blocked then
  458.               hData.handle = filesystems[data.index].open(hData.path, hData.m)
  459.               hData.seek(hData.handle, "set", hData.localPos)
  460.             elseif hData.mode == "r" then
  461.               hData.handle = nil
  462.             end
  463.           end
  464.         end
  465.         file.to = file.from
  466.       end
  467.     else--if mode == "a" then
  468.       -- Seek to end of file
  469.       for i = data.file.from, data.file.to do
  470.         data.localPos = filesystems[i].size(path)
  471.         data.globalPos = data.globalPos + data.localPos
  472.       end
  473.     end
  474.   end
  475.   handles[data.handle] = data
  476.   return data.handle
  477. end
  478.  
  479. local function writeValue(data, value, count)
  480.   local limited = count ~= math.huge
  481.   count = math.min(count, #value - data.start)
  482.   value = (limited or data.start > 0) and
  483.    value:sub(data.start+1, data.start + count) or value
  484.  
  485.   if count > 0 then
  486.     -- read data from the current file
  487.     local ok, err = filesystems[data.index].write(data.handle, value)
  488.     if ok then
  489.       data.start = data.start + count
  490.       data.globalPos = data.globalPos + count
  491.       data.localPos = data.localPos + count
  492.     elseif err ~= "not enough space" then
  493.       return err
  494.     end
  495.   end
  496.  
  497.   if limited then
  498.     -- open next file
  499.     filesystems[data.index].close(data.handle)
  500.     data.index = data.index+1
  501.     data.localPos = 0
  502.     local err = writeOpenNext(data)
  503.     if err then
  504.       return err
  505.     elseif data.file.to < data.index then
  506.       data.file.to = data.index
  507.     elseif data.mode == "a" then
  508.       filesystems[data.index].seek(data.handle, "set", 0)
  509.     end
  510.   end
  511.   return nil
  512. end
  513.  
  514. function cmp.write(handle, value)
  515.   checkArg(2, value, "string")
  516.  
  517.   local data = handles[handle]
  518.   if not data or data.mode == "r" then
  519.     return nil, "bad file descriptor"
  520.   elseif not data.handle then
  521.     return nil, "not enough space"
  522.   elseif value == "" then
  523.     return true
  524.   elseif #value > MAX_IO then
  525.     value = value:sub(1, MAX_IO)
  526.   end
  527.   data.start = 0
  528.  
  529.   local err
  530.   if data.mode == "a" then
  531.     while data.file.to > data.index do
  532.       err = writeValue(data, value,
  533.        filesystems[data.index].size(data.path) - data.localPos)
  534.       if err then
  535.         return false, err
  536.       elseif data.start >= #value then
  537.         return true
  538.       end
  539.     end
  540.   end
  541.  
  542.   while true do
  543.     err = writeValue(data, value, math.huge)
  544.     if err then
  545.       return false, err
  546.     elseif data.start >= #value then
  547.       return true
  548.     end
  549.  
  550.     err = writeValue(data, value, filesystems[data.index].spaceTotal()
  551.      - filesystems[data.index].spaceUsed() - FILE_COST)
  552.     if err then
  553.       return false, err
  554.     elseif data.start >= #value then
  555.       return true
  556.     end
  557.   end
  558.   return false
  559. end
  560.  
  561. function cmp.read(handle, count)
  562.   checkArg(2, count, "number")
  563.  
  564.   local data = handles[handle]
  565.   if not data or data.mode ~= "r" then
  566.     return nil, "bad file descriptor"
  567.   elseif data.index > data.file.to then
  568.     return nil
  569.   elseif count <= 0 then
  570.     return ""
  571.   elseif count > MAX_IO then
  572.     count = MAX_IO
  573.   end
  574.  
  575.   local output = {}
  576.   while true do
  577.     local result = filesystems[data.index].read(data.handle, count)
  578.     if result then
  579.       output[#output+1] = result
  580.       data.globalPos = data.globalPos + #result
  581.       data.localPos = data.localPos + #result
  582.       count = count - #result
  583.       if count <= 0 then
  584.         break
  585.       end
  586.     end
  587.  
  588.     filesystems[data.index].close(data.handle)
  589.     data.index = data.index+1
  590.     if data.file.to >= data.index then
  591.       break
  592.     end
  593.     data.localPos = 0
  594.     if not readOpenNext(data) then
  595.       break
  596.     end
  597.   end
  598.   return table.concat(output)
  599. end
  600.  
  601. function cmp.seek(handle, whence, offset)
  602.   checkArg(2, whence, "string")
  603.   checkArg(3, offset, "number")
  604.   offset = math.floor(offset)
  605.  
  606.   local data = handles[handle]
  607.   if not (data and data.handle) then
  608.     return nil, "bad file descriptor"
  609.   end
  610.  
  611.   local size, index, globalPos = 0, 1, 0
  612.   if whence == "set" or whence == "cur" then
  613.     if whence == "set" then
  614.       globalPos = offset
  615.     else
  616.       if offset == 0 then
  617.         return data.globalPos
  618.       end
  619.       globalPos = data.globalPos + offset
  620.     end
  621.     index = 1
  622.     for i = data.file.from, data.file.to-1 do
  623.       local newSize = size + filesystems[i].size(data.path)
  624.       if newSize > globalPos then
  625.         break
  626.       end
  627.       index = i+1
  628.       size = newSize
  629.     end
  630.     offset = globalPos - size
  631.   elseif whence == "end" then
  632.     index = data.file.to
  633.     for i = data.file.to, data.file.from, -1 do
  634.       local newSize = size + filesystems[i].size(data.path)
  635.       if newSize >= -offset then
  636.         index = i
  637.         offset = newSize + offset
  638.         globalPos = offset
  639.         for j = data.file.from, i-1 do
  640.           globalPos = globalPos + filesystems[j].size(data.path)
  641.         end
  642.         break
  643.       end
  644.       size = newSize
  645.     end
  646.   else
  647.     error("invalid mode", 2)
  648.   end
  649.   if globalPos < 0 then
  650.     error("bad argument", 2)
  651.   end
  652.  
  653.   if data.index ~= index then
  654.     filesystems[data.index].close(data.handle)
  655.     data.index = index
  656.     if data.mode == "r" then
  657.       readOpenNext(data)
  658.     else
  659.       writeOpenNext(data)
  660.     end
  661.   end
  662.   filesystems[index].seek(data.handle, "set", offset)
  663.   data.localPos = offset
  664.   data.globalPos = globalPos
  665.   return globalPos
  666. end
  667.  
  668. function cmp.close(handle)
  669.   local data = handles[handle]
  670.   if not data then
  671.     return nil, "bad file descriptor"
  672.   elseif data.handle then
  673.     filesystems[data.index].close(data.handle)
  674.   end
  675.   handles[handle] = nil
  676. end
  677.  
  678. function cmp.lastModified(path)
  679.   checkArg(1, path, "string")
  680.  
  681.   local file, name
  682.   path, file, name = findDir(path)
  683.   file = file and file[name]
  684.   if not file then
  685.     return 0
  686.   end
  687.   path = path .. "/" .. name
  688.  
  689.   local tstmp = 0
  690.   for i = file.isFile == true and file.to or 1,
  691.    file.isFile == true and file.from or #filesystems do
  692.     tstmp = math.max(tstmp, filesystems[i].lastModified(path))
  693.   end
  694.   return tstmp
  695. end
  696.  
  697. -----------------------------
  698. -- component API extension --
  699. -----------------------------
  700.  
  701. local newApi = {}
  702.  
  703. function newApi.doc(address, method)
  704.   checkArg(1, address, "string")
  705.   checkArg(2, method, "string")
  706.   return nativeComponent.doc(
  707.    address == ADDRESS and otherAddress or address, method)
  708. end
  709.  
  710. function newApi.invoke(address, name, ...)
  711.   checkArg(1, address, "string")
  712.   if address ~= ADDRESS then
  713.     return nativeComponent.invoke(address, name, ...)
  714.   end
  715.   if type(cmp[name]) ~= "function" then
  716.     error("no such method", 2)
  717.   end
  718.   local ok, res1, res2 = pcall(cmp[name], ...)
  719.   if ok then
  720.     return res1, res2
  721.   else
  722.     error(res1, 2)
  723.   end
  724. end
  725.  
  726. function newApi.list(filter, exact)
  727.   checkArg(1, filter, "string", "nil")
  728.   local list = nativeComponent.list(filter, exact)
  729.  
  730.   if not filter or (filter == string.sub("filesystem", 1, #filter) and
  731.    (not exact or #filter == 10)) then
  732.     list[ADDRESS] = "filesystem"
  733.   end
  734.   return list
  735. end
  736.  
  737. function newApi.methods(address)
  738.   checkArg(1, address, "string")
  739.   return nativeComponent.methods(
  740.    address == ADDRESS and otherAddress or address)
  741. end
  742.  
  743. function newApi.proxy(address)
  744.   checkArg(1, address, "string")
  745.   if address == ADDRESS then
  746.     return cmp
  747.   end
  748.   return nativeComponent.proxy(address)
  749. end
  750.  
  751. function newApi.type(address)
  752.   checkArg(1, address, "string")
  753.   if address == ADDRESS then
  754.     return "filesystem"
  755.   end
  756.   return nativeComponent.type(address)
  757. end
  758.  
  759. function newApi.slot(address)
  760.   checkArg(1, address, "string")
  761.   if address == ADDRESS then
  762.     return -1
  763.   end
  764.   return nativeComponent.slot(address)
  765. end
  766.  
  767. function newApi.fields(address)
  768.   checkArg(1, address, "string")
  769.   return nativeComponent.fields(
  770.    address == ADDRESS and otherAddress or address)
  771. end
  772.  
  773. ----------------
  774. -- rc methods --
  775. ----------------
  776.  
  777. --[[
  778. Adds a file system to the bundled one which wipes the added system.
  779. Don't use this system for any other purpose afterwards.
  780. ]]--
  781. function add(address)
  782.   if type(address) ~= "string" then
  783.     io.stderr:write("expected a string\n")
  784.     return
  785.   end
  786.  
  787.   local proxy
  788.   for a in component.list("filesystem", true) do
  789.     if a ~= ADDRESS and (a:sub(1, #address) == address or
  790.      component.invoke(a, "getLabel") == address) then
  791.       if proxy then
  792.         io.stderr:write("address is ambiguous")
  793.         return
  794.       end
  795.       proxy = component.proxy(a)
  796.     end
  797.   end
  798.   if not proxy then
  799.     io.stderr:write("cannot find filesystem component\n")
  800.     return
  801.   elseif proxy.isReadOnly() then
  802.     io.stderr:write("cannot add a read-only filesystem\n")
  803.     return
  804.   elseif proxy.exists(".bundle_metadata") then
  805.     io.stderr:write("cannot add a filesystem twice")
  806.     return
  807.   end
  808.  
  809.   proxy.remove("/")
  810.   if not filesystems[1] then
  811.     label = proxy.getLabel()
  812.   end
  813.   local index = #filesystems+1
  814.   filesystems[index] = proxy
  815.   spaceTotal = spaceTotal + proxy.spaceTotal()
  816.   mark(index)
  817.   io.stdout:write("added filesystem successfully: " .. proxy.address .. "\n")
  818. end
  819.  
  820. --[[
  821. Removes either the specified filesystem or the last added filesystem
  822. from the bundle one as long as it causes no
  823. loss of data. If you want to remove it anyway pass true as second parameter.
  824. Note that the drive isn't wiped after removal.
  825. To remove the last added filesystem, pass . for the address.
  826. ]]--
  827. function remove(address, forcefully)
  828.   if not running then
  829.     io.stderr:write("bundle isn't running\n")
  830.     return
  831.   end
  832.   -- TODO: allow relocation of data from every filesystem.
  833.  
  834.   -- Define error message here to avoid redundant messages.
  835.   local errMsg = "removing the filesystem causes the loss of data. Pass true as first parameter to remove it anyway"
  836.  
  837.   local fs
  838.   if address == "." then
  839.     local index = #filesystems
  840.     fs = filesystems[index]
  841.     if #fs.list("") > 1 and not forcefully then
  842.       io.stderr:write(errMsg)
  843.       return
  844.     end
  845.   else
  846.     if address:len() == 8 then
  847.       -- Short address
  848.       for i=1, #filesystems do
  849.         if filesystems[i].address:sub(0, 8) == address then
  850.           fs = filesystems[i]
  851.           if fs.list("") > 1 and not forcefully then
  852.             io.stderr:write(errMsg)
  853.             return
  854.           end
  855.         end
  856.       end
  857.     else
  858.       for i=1, #filesystems do
  859.         if filesystems[i].address == address then
  860.           fs = filesystems[i]
  861.           if fs.list("") > 1 and not forcefully then
  862.             io.stderr:write(errMsg)
  863.             return
  864.           end
  865.         end
  866.       end
  867.     end
  868.   end
  869.  
  870.   if fs == nil then
  871.     io.stderr:write("Specified filesystem '" .. address .. "' not found.")
  872.     return
  873.   end
  874.  
  875.   local function truncate(dir)
  876.     for name, element in next, dir do
  877.       if element.isFile ~= true then
  878.         truncate(element)
  879.       else
  880.         if element.from == index then
  881.           dir[name] = nil
  882.         elseif element.to == index then
  883.           element.to = index-1
  884.         end
  885.       end
  886.     end
  887.   end
  888.  
  889.   fs.remove(".bundle_metadata")
  890.   truncate(structure)
  891.   io.stdout:write("removed filesystem successfully: " .. fs.address .. "\n")
  892. end
  893.  
  894. function list()
  895.   if not running then
  896.     io.stderr:write("bundle isn't running\n")
  897.     return
  898.   end
  899.   io.stdout:write(string.format("Address%sLabel\n", string.rep(" ", 30)))
  900.   for i = 1, #filesystems do
  901.     io.stdout:write(string.format("%s %s\n",
  902.      filesystems[i].address, filesystems[i].getLabel()))
  903.   end
  904. end
  905.  
  906. function start()
  907.   if running then
  908.     io.stderr:write("bundle is running already\n")
  909.     return
  910.   end
  911.  
  912.   -- find file systems
  913.   for address in component.list("filesystem", true) do
  914.     local proxy = component.proxy(address)
  915.     if not proxy.isReadOnly() then
  916.       local handle = proxy.open(".bundle_metadata", "r")
  917.       if handle then
  918.         local file = proxy.read(handle, 40)
  919.         proxy.close(handle)
  920.         if file:sub(1, 36) == ADDRESS then
  921.           filesystems[ tonumber(file:sub(37, 40), 16) ] = proxy
  922.         end
  923.       end
  924.     end
  925.   end
  926.   if not filesystems[1] then
  927.     io.stderr:write("cannot find any bundle filesystem\n")
  928.     return
  929.   end
  930.  
  931.   -- import file systems into virtual file system
  932.   local pos = 1
  933.   local proxy
  934.   local function addFilesystem(path, struc)
  935.     local list = proxy.list(path)
  936.     for i = 1, #list do
  937.       local name = CASE_INSENSITIVE and list[i]:lower() or list[i]
  938.       local currPath = path .. name
  939.       if proxy.isDirectory(currPath) then
  940.         name = name:sub(1, -2) -- remove trailing slash
  941.         struc[name] = addFilesystem(currPath, struc[name] or {})
  942.       else
  943.         if struc[name] then
  944.           struc[name].to = pos
  945.         else
  946.           struc[name] = {
  947.             isFile = true,
  948.             from = pos,
  949.             to = pos,
  950.           }
  951.         end
  952.       end
  953.     end
  954.     return struc
  955.   end
  956.   while pos <= #filesystems do
  957.     proxy = filesystems[pos]
  958.     spaceTotal = spaceTotal + proxy.spaceTotal()
  959.     addFilesystem("", structure)
  960.     pos = pos+1
  961.   end
  962.   structure[".bundle_metadata"] = nil
  963.   label = filesystems[1].getLabel()
  964.  
  965.   -- overwrite component API
  966.   for name, func in next, newApi do
  967.     nativeComponent[name] = component[name]
  968.     component[name] = func
  969.   end
  970.   require("computer").pushSignal("component_added", ADDRESS, "filesystem")
  971.  
  972.   io.stdout:write("launched bundle successfully\n")
  973.   running = true
  974. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement