Advertisement
Omsigames

grapes/Filesystem.lua

Dec 25th, 2024 (edited)
30
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.31 KB | None | 0 0
  1. -- local paths = require("Paths")
  2. local event = require("event")
  3. local component = require("component")
  4. local bit32 = require("bit32")
  5.  
  6. --------------------------------------------------------------------------------
  7.  
  8. local Filesystem = {
  9.     SORTING_NAME = 1,
  10.     SORTING_TYPE = 2,
  11.     SORTING_DATE = 3,
  12. }
  13.  
  14. local BUFFER_SIZE = 1024
  15. local BOOT_PROXY
  16.  
  17. local mountedProxies = {}
  18.  
  19. --------------------------------------- String-related path processing -----------------------------------------
  20.  
  21. function Filesystem.path(path)
  22.     return path:match("^(.+%/).") or ""
  23. end
  24.  
  25. function Filesystem.name(path)
  26.     return path:match("%/?([^%/]+%/?)$")
  27. end
  28.  
  29. function Filesystem.extension(path, lower)
  30.     return path:match("[^%/]+(%.[^%/]+)%/?$")
  31. end
  32.  
  33. function Filesystem.hideExtension(path)
  34.     return path:match("(.+)%..+") or path
  35. end
  36.  
  37. function Filesystem.isHidden(path)
  38.     if path:sub(1, 1) == "." then
  39.         return true
  40.     end
  41.  
  42.     return false
  43. end
  44.  
  45. function Filesystem.removeSlashes(path)
  46.     return path:gsub("/+", "/")
  47. end
  48.  
  49. --------------------------------------- Mounted Filesystem support -----------------------------------------
  50.  
  51. function Filesystem.mount(cyka, path)
  52.     if type(cyka) == "table" then
  53.         local mountedProxy
  54.  
  55.         for i = 1, #mountedProxies do
  56.             mountedProxy = mountedProxies[i]
  57.  
  58.             if mountedProxy.path == path then
  59.                 return false, "mount path has been taken by other mounted Filesystem"
  60.             elseif mountedProxy.proxy == cyka then
  61.                 return false, "proxy is already mounted"
  62.             end
  63.         end
  64.  
  65.         table.insert(mountedProxies, {
  66.             path = path,
  67.             proxy = cyka
  68.         })
  69.  
  70.         table.sort(mountedProxies, function(a, b) return #b.path < #a.path end)
  71.  
  72.         return true
  73.     else
  74.         error("bad argument #1 (Filesystem proxy expected, got " .. tostring(cyka) .. ")")
  75.     end
  76. end
  77.  
  78. function Filesystem.unmount(cyka)
  79.     if type(cyka) == "table" then
  80.         for i = 1, #mountedProxies do
  81.             if mountedProxies[i].proxy == cyka then
  82.                 table.remove(mountedProxies, i)
  83.                 return true
  84.             end
  85.         end
  86.  
  87.         return false, "specified proxy is not mounted"
  88.     elseif type(cyka) == "string" then
  89.         for i = 1, #mountedProxies do
  90.             if mountedProxies[i].proxy.address == cyka then
  91.                 table.remove(mountedProxies, i)
  92.                 return true
  93.             end
  94.         end
  95.        
  96.         return false, "specified proxy address is not mounted"
  97.     else
  98.         error("bad argument #1 (Filesystem proxy or mounted path expected, got " .. tostring(cyka) .. ")")
  99.     end
  100. end
  101.  
  102. function Filesystem.get(path)
  103.     checkArg(1, path, "string")
  104.  
  105.     for i = 1, #mountedProxies do
  106.         if path:sub(1, unicode.len(mountedProxies[i].path)) == mountedProxies[i].path then
  107.             return mountedProxies[i].proxy, unicode.sub(path, mountedProxies[i].path:len() + 1, -1)
  108.         end
  109.     end
  110.  
  111.     return BOOT_PROXY, path
  112. end
  113.  
  114. function Filesystem.mounts()
  115.     local key, value
  116.     return function()
  117.         key, value = next(mountedProxies, key)
  118.         if value then
  119.             return value.proxy, value.path
  120.         end
  121.     end
  122. end
  123.  
  124. --------------------------------------- I/O methods -----------------------------------------
  125.  
  126. local function readString(self, count)
  127.     -- If current buffer content is a "part" of "count of data" we need to read
  128.     if count > #self.buffer then
  129.         local data, chunk = self.buffer
  130.  
  131.         while #data < count do
  132.             chunk = self.proxy.read(self.stream, BUFFER_SIZE)
  133.  
  134.             if chunk then
  135.                 data = data .. chunk
  136.             else
  137.                 self.position = self:seek("end", 0)
  138.  
  139.                 -- EOF at start
  140.                 if data == "" then
  141.                     return nil
  142.                 -- EOF after read
  143.                 else
  144.                     return data
  145.                 end
  146.             end
  147.         end
  148.  
  149.         self.buffer = data:sub(count + 1, -1)
  150.         chunk = data:sub(1, count)
  151.         self.position = self.position + #chunk
  152.  
  153.         return chunk
  154.     else
  155.         local data = self.buffer:sub(1, count)
  156.         self.buffer = self.buffer:sub(count + 1, -1)
  157.         self.position = self.position + count
  158.  
  159.         return data
  160.     end
  161. end
  162.  
  163. local function readLine(self)
  164.     local data = ""
  165.     while true do
  166.         if #self.buffer > 0 then
  167.             local starting, ending = self.buffer:find("\n")
  168.             if starting then
  169.                 local chunk = self.buffer:sub(1, starting - 1)
  170.                 self.buffer = self.buffer:sub(ending + 1, -1)
  171.                 self.position = self.position + #chunk
  172.  
  173.                 return data .. chunk
  174.             else
  175.                 data = data .. self.buffer
  176.             end
  177.         end
  178.  
  179.         local chunk = self.proxy.read(self.stream, BUFFER_SIZE)
  180.         if chunk then
  181.             self.buffer = chunk
  182.             self.position = self.position + #chunk
  183.         -- EOF
  184.         else
  185.             local data = self.buffer
  186.             self.position = self:seek("end", 0)
  187.  
  188.             return #data > 0 and data or nil
  189.         end
  190.     end
  191. end
  192.  
  193. local function lines(self)
  194.     return function()
  195.         local line = readLine(self)
  196.         if line then
  197.             return line
  198.         else
  199.             self:close()
  200.         end
  201.     end
  202. end
  203.  
  204. local function readAll(self)
  205.     local data, chunk = ""
  206.     while true do
  207.         chunk = self.proxy.read(self.stream, 4096)
  208.         if chunk then
  209.             data = data .. chunk
  210.         -- EOF
  211.         else
  212.             self.position = self:seek("end", 0)
  213.             return data
  214.         end
  215.     end
  216. end
  217.  
  218. local function readBytes(self, count, littleEndian)
  219.     if count == 1 then
  220.         local data = readString(self, 1)
  221.         if data then
  222.             return string.byte(data)
  223.         end
  224.  
  225.         return nil
  226.     else
  227.         local bytes, result = {string.byte(readString(self, count) or "\x00", 1, 8)}, 0
  228.  
  229.         if littleEndian then
  230.             for i = #bytes, 1, -1 do
  231.                 result = bit32.bor(bit32.lshift(result, 8), bytes[i])
  232.             end
  233.         else
  234.             for i = 1, #bytes do
  235.                 result = bit32.bor(bit32.lshift(result, 8), bytes[i])
  236.             end
  237.         end
  238.  
  239.         return result
  240.     end
  241. end
  242.  
  243. local function readUnicodeChar(self)
  244.     local byteArray = {string.byte(readString(self, 1))}
  245.  
  246.     local nullBitPosition = 0
  247.     for i = 1, 7 do
  248.         if bit32.band(bit32.rshift(byteArray[1], 8 - i), 0x1) == 0x0 then
  249.             nullBitPosition = i
  250.             break
  251.         end
  252.     end
  253.  
  254.     for i = 1, nullBitPosition - 2 do
  255.         table.insert(byteArray, string.byte(readString(self, 1)))
  256.     end
  257.  
  258.     return string.char(table.unpack(byteArray))
  259. end
  260.  
  261. local function read(self, format, ...)
  262.     local formatType = type(format)
  263.     if formatType == "number" then 
  264.         return readString(self, format)
  265.     elseif formatType == "string" then
  266.         format = format:gsub("^%*", "")
  267.  
  268.         if format == "a" then
  269.             return readAll(self)
  270.         elseif format == "l" then
  271.             return readLine(self)
  272.         elseif format == "b" then
  273.             return readBytes(self, 1)
  274.         elseif format == "bs" then
  275.             return readBytes(self, ...)
  276.         elseif format == "u" then
  277.             return readUnicodeChar(self)
  278.         else
  279.             error("bad argument #2 ('a' (whole file), 'l' (line), 'u' (unicode char), 'b' (byte as number) or 'bs' (sequence of n bytes as number) expected, got " .. format .. ")")
  280.         end
  281.     else
  282.         error("bad argument #1 (number or string expected, got " .. formatType ..")")
  283.     end
  284. end
  285.  
  286. local function seek(self, pizda, cyka)
  287.     if pizda == "set" then
  288.         local result, reason = self.proxy.seek(self.stream, "set", cyka)
  289.         if result then
  290.             self.position = result
  291.             self.buffer = ""
  292.         end
  293.  
  294.         return result, reason
  295.     elseif pizda == "cur" then
  296.         local result, reason = self.proxy.seek(self.stream, "set", self.position + cyka)
  297.         if result then
  298.             self.position = result
  299.             self.buffer = ""
  300.         end
  301.  
  302.         return result, reason
  303.     elseif pizda == "end" then
  304.         local result, reason = self.proxy.seek(self.stream, "end", cyka)
  305.         if result then
  306.             self.position = result
  307.             self.buffer = ""
  308.         end
  309.  
  310.         return result, reason
  311.     else
  312.         error("bad argument #2 ('set', 'cur' or 'end' expected, got " .. tostring(pizda) .. ")")
  313.     end
  314. end
  315.  
  316. local function write(self, ...)
  317.     local data = {...}
  318.     for i = 1, #data do
  319.         data[i] = tostring(data[i])
  320.     end
  321.     data = table.concat(data)
  322.  
  323.     -- Data is small enough to fit buffer
  324.     if #data < (BUFFER_SIZE - #self.buffer) then
  325.         self.buffer = self.buffer .. data
  326.  
  327.         return true
  328.     else
  329.         -- Write current buffer content
  330.         local success, reason = self.proxy.write(self.stream, self.buffer)
  331.         if success then
  332.             -- If data will not fit buffer, use iterative writing with data partitioning
  333.             if #data > BUFFER_SIZE then
  334.                 for i = 1, #data, BUFFER_SIZE do
  335.                     success, reason = self.proxy.write(self.stream, data:sub(i, i + BUFFER_SIZE - 1))
  336.                    
  337.                     if not success then
  338.                         break
  339.                     end
  340.                 end
  341.  
  342.                 self.buffer = ""
  343.  
  344.                 return success, reason
  345.             -- Data will perfectly fit in empty buffer
  346.             else
  347.                 self.buffer = data
  348.  
  349.                 return true
  350.             end
  351.         else
  352.             return false, reason
  353.         end
  354.     end
  355. end
  356.  
  357. local function writeBytes(self, ...)
  358.     return write(self, string.char(...))
  359. end
  360.  
  361. local function close(self)
  362.     if self.write and #self.buffer > 0 then
  363.         self.proxy.write(self.stream, self.buffer)
  364.     end
  365.  
  366.     return self.proxy.close(self.stream)
  367. end
  368.  
  369. function Filesystem.open(path, mode)
  370.     local proxy, proxyPath = Filesystem.get(path)
  371.     local result, reason = proxy.open(proxyPath, mode)
  372.     if result then
  373.         local handle = {
  374.             proxy = proxy,
  375.             stream = result,
  376.             position = 0,
  377.             buffer = "",
  378.             close = close,
  379.             seek = seek,
  380.         }
  381.  
  382.         if mode == "r" or mode == "rb" then
  383.             handle.readString = readString
  384.             handle.readUnicodeChar = readUnicodeChar
  385.             handle.readBytes = readBytes
  386.             handle.readLine = readLine
  387.             handle.lines = lines
  388.             handle.readAll = readAll
  389.             handle.read = read
  390.  
  391.             return handle
  392.         elseif mode == "w" or mode == "wb" or mode == "a" or mode == "ab" then
  393.             handle.write = write
  394.             handle.writeBytes = writeBytes
  395.  
  396.             return handle
  397.         else
  398.             error("bad argument #2 ('r', 'rb', 'w', 'wb' or 'a' expected, got )" .. tostring(mode) .. ")")
  399.         end
  400.     else
  401.         return nil, reason
  402.     end
  403. end
  404.  
  405. --------------------------------------- Rest proxy methods -----------------------------------------
  406.  
  407. function Filesystem.exists(path)
  408.     local proxy, proxyPath = Filesystem.get(path)
  409.     return proxy.exists(proxyPath)
  410. end
  411.  
  412. function Filesystem.size(path)
  413.     local proxy, proxyPath = Filesystem.get(path)
  414.     return proxy.size(proxyPath)
  415. end
  416.  
  417. function Filesystem.isDirectory(path)
  418.     local proxy, proxyPath = Filesystem.get(path)
  419.     return proxy.isDirectory(proxyPath)
  420. end
  421.  
  422. function Filesystem.makeDirectory(path)
  423.     local proxy, proxyPath = Filesystem.get(path)
  424.     return proxy.makeDirectory(proxyPath)
  425. end
  426.  
  427. function Filesystem.lastModified(path)
  428.     local proxy, proxyPath = Filesystem.get(path)
  429.     return proxy.lastModified(proxyPath)
  430. end
  431.  
  432. function Filesystem.remove(path)
  433.     local proxy, proxyPath = Filesystem.get(path)
  434.     return proxy.remove(proxyPath)
  435. end
  436.  
  437. function Filesystem.list(path, sortingMethod)
  438.     local proxy, proxyPath = Filesystem.get(path)
  439.    
  440.     local list, reason = proxy.list(proxyPath) 
  441.     if list then
  442.         -- Fullfill list with mounted paths if needed
  443.         for i = 1, #mountedProxies do
  444.             if path == Filesystem.path(mountedProxies[i].path) then
  445.                 table.insert(list, Filesystem.name(mountedProxies[i].path))
  446.             end
  447.         end
  448.  
  449.         -- Applying sorting methods
  450.         if not sortingMethod or sortingMethod == Filesystem.SORTING_NAME then
  451.             table.sort(list, function(a, b)
  452.                 return unicode.lower(a) < unicode.lower(b)
  453.             end)
  454.  
  455.             return list
  456.         elseif sortingMethod == Filesystem.SORTING_DATE then
  457.             table.sort(list, function(a, b)
  458.                 return Filesystem.lastModified(path .. a) > Filesystem.lastModified(path .. b)
  459.             end)
  460.  
  461.             return list
  462.         elseif sortingMethod == Filesystem.SORTING_TYPE then
  463.             -- Creating a map with "extension" = {file1, file2, ...} structure
  464.             local map, extension = {}
  465.             for i = 1, #list do
  466.                 extension = Filesystem.extension(list[i]) or "Z"
  467.                
  468.                 -- If it's a directory without extension
  469.                 if extension:sub(1, 1) ~= "." and Filesystem.isDirectory(path .. list[i]) then
  470.                     extension = "."
  471.                 end
  472.  
  473.                 map[extension] = map[extension] or {}
  474.                 table.insert(map[extension], list[i])
  475.             end
  476.  
  477.             -- Sorting lists for each extension
  478.             local extensions = {}
  479.             for key, value in pairs(map) do
  480.                 table.sort(value, function(a, b)
  481.                     return unicode.lower(a) < unicode.lower(b)
  482.                 end)
  483.  
  484.                 table.insert(extensions, key)
  485.             end
  486.  
  487.             -- Sorting extensions
  488.             table.sort(extensions, function(a, b)
  489.                 return unicode.lower(a) < unicode.lower(b)
  490.             end)
  491.  
  492.             -- Fullfilling final list
  493.             list = {}
  494.             for i = 1, #extensions do
  495.                 for j = 1, #map[extensions[i]] do
  496.                     table.insert(list, map[extensions[i]][j])
  497.                 end
  498.             end
  499.  
  500.             return list
  501.         end
  502.     end
  503.  
  504.     return list, reason
  505. end
  506.  
  507. function Filesystem.rename(fromPath, toPath)
  508.     local fromProxy, fromProxyPath = Filesystem.get(fromPath)
  509.     local toProxy, toProxyPath = Filesystem.get(toPath)
  510.  
  511.     -- If it's the same Filesystem component
  512.     if fromProxy.address == toProxy.address then
  513.         return fromProxy.rename(fromProxyPath, toProxyPath)
  514.     else
  515.         -- Copy files to destination
  516.         Filesystem.copy(fromPath, toPath)
  517.         -- Remove original files
  518.         Filesystem.remove(fromPath)
  519.     end
  520. end
  521.  
  522. --------------------------------------- Advanced methods -----------------------------------------
  523.  
  524. function Filesystem.copy(fromPath, toPath)
  525.     local function copyRecursively(fromPath, toPath)
  526.         if Filesystem.isDirectory(fromPath) then
  527.             Filesystem.makeDirectory(toPath)
  528.  
  529.             local list = Filesystem.list(fromPath)
  530.             for i = 1, #list do
  531.                 copyRecursively(fromPath .. "/" .. list[i], toPath .. "/" .. list[i])
  532.             end
  533.         else
  534.             local fromHandle = Filesystem.open(fromPath, "rb")
  535.             if fromHandle then
  536.                 local toHandle = Filesystem.open(toPath, "wb")
  537.                 if toHandle then
  538.                     while true do
  539.                         local chunk = readString(fromHandle, BUFFER_SIZE)
  540.                         if chunk then
  541.                             if not write(toHandle, chunk) then
  542.                                 break
  543.                             end
  544.                         else
  545.                             toHandle:close()
  546.                             fromHandle:close()
  547.  
  548.                             break
  549.                         end
  550.                     end
  551.                 end
  552.             end
  553.         end
  554.     end
  555.  
  556.     copyRecursively(fromPath, toPath)
  557. end
  558.  
  559. function Filesystem.read(path)
  560.     local handle, reason = Filesystem.open(path, "rb")
  561.     if handle then
  562.         local data = readAll(handle)
  563.         handle:close()
  564.  
  565.         return data
  566.     end
  567.  
  568.     return false, reason
  569. end
  570.  
  571. function Filesystem.lines(path)
  572.     local handle, reason = Filesystem.open(path, "rb")
  573.     if handle then
  574.         return handle:lines()
  575.     else
  576.         error(reason)
  577.     end
  578. end
  579.  
  580. function Filesystem.readLines(path)
  581.     local handle, reason = Filesystem.open(path, "rb")
  582.     if handle then
  583.         local lines, index, line = {}, 1
  584.  
  585.         repeat
  586.             line = readLine(handle)
  587.             lines[index] = line
  588.             index = index + 1
  589.         until not line
  590.  
  591.         handle:close()
  592.  
  593.         return lines
  594.     end
  595.  
  596.     return false, reason
  597. end
  598.  
  599. local function writeOrAppend(append, path, ...)
  600.     Filesystem.makeDirectory(Filesystem.path(path))
  601.    
  602.     local handle, reason = Filesystem.open(path, append and "ab" or "wb")
  603.     if handle then
  604.         local result, reason = write(handle, ...)
  605.         handle:close()
  606.  
  607.         return result, reason
  608.     end
  609.  
  610.     return false, reason
  611. end
  612.  
  613. function Filesystem.write(path, ...)
  614.     return writeOrAppend(false, path,...)
  615. end
  616.  
  617. function Filesystem.append(path, ...)
  618.     return writeOrAppend(true, path, ...)
  619. end
  620.  
  621. function Filesystem.writeTable(path, ...)
  622.     return Filesystem.write(path, require("Text").serialize(...))
  623. end
  624.  
  625. function Filesystem.readTable(path)
  626.     local result, reason = Filesystem.read(path)
  627.     if result then
  628.         return require("Text").deserialize(result)
  629.     end
  630.  
  631.     return result, reason
  632. end
  633.  
  634. function Filesystem.setProxy(proxy)
  635.     BOOT_PROXY = proxy
  636. end
  637.  
  638. function Filesystem.getProxy()
  639.     return BOOT_PROXY
  640. end
  641.  
  642. -- --------------------------------------- loadfile() and dofile() implementation -----------------------------------------
  643.  
  644. -- function loadfile(path)
  645. --  local data, reason = Filesystem.read(path)
  646. --  if data then
  647. --      return load(data, "=" .. path)
  648. --  end
  649.  
  650. --  return nil, reason
  651. -- end
  652.  
  653. -- function dofile(path, ...)
  654. --  local result, reason = loadfile(path)
  655. --  if result then
  656. --      local data = {xpcall(result, debug.traceback, ...)}
  657. --      if data[1] then
  658. --          return table.unpack(data, 2)
  659. --      else
  660. --          error(data[2])
  661. --      end
  662. --  else
  663. --      error(reason)
  664. --  end
  665. -- end
  666.  
  667. -- --------------------------------------------------------------------------------
  668.  
  669. -- Mount all existing Filesystem components
  670. for address in component.list("Filesystem") do
  671.     Filesystem.mount(component.proxy(address), "/mnt/" .. address .. "/")
  672. end
  673.  
  674. -- Automatically mount/unmount Filesystem components
  675. event.listen("component_added", function(signal, address, type)
  676.     if signal == "component_added" and type == "Filesystem" then
  677.         Filesystem.mount(component.proxy(address), "/mnt/" .. address .. "/")
  678.     end
  679. end)
  680.  
  681. event.listen("component_removed", function(signal, address, type)
  682.     if signal == "component_removed" and type == "Filesystem" then
  683.         Filesystem.unmount(address)
  684.     end
  685. end)
  686.  
  687. --------------------------------------------------------------------------------
  688.  
  689. return Filesystem
  690.  
Tags: ORMS
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement