mirror of
https://github.com/IgorTimofeev/MineOS.git
synced 2026-04-04 15:12:46 +02:00
Переходим на MineOS Standalone #1
This commit is contained in:
655
Libraries/Filesystem.lua
Normal file
655
Libraries/Filesystem.lua
Normal file
@@ -0,0 +1,655 @@
|
||||
|
||||
local paths = require("Paths")
|
||||
local event = require("Event")
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local filesystem = {
|
||||
SORTING_NAME = 1,
|
||||
SORTING_TYPE = 2,
|
||||
SORTING_DATE = 3,
|
||||
}
|
||||
|
||||
local BUFFER_SIZE = 1024
|
||||
local BOOT_PROXY
|
||||
|
||||
local mountedProxies = {}
|
||||
|
||||
--------------------------------------- String-related path processing -----------------------------------------
|
||||
|
||||
function filesystem.path(path)
|
||||
return path:match("^(.+%/).") or ""
|
||||
end
|
||||
|
||||
function filesystem.name(path)
|
||||
return path:match("%/?([^%/]+)%/?$")
|
||||
end
|
||||
|
||||
function filesystem.extension(path, lower)
|
||||
return path:match("[^%/]+(%.[^%/]+)%/?$")
|
||||
end
|
||||
|
||||
function filesystem.hideExtension(path)
|
||||
return path:match("(.+)%..+") or path
|
||||
end
|
||||
|
||||
function filesystem.isHidden(path)
|
||||
if path:sub(1, 1) == "." then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function filesystem.removeSlashes(path)
|
||||
return path:gsub("/+", "/")
|
||||
end
|
||||
|
||||
--------------------------------------- Mounted filesystem support -----------------------------------------
|
||||
|
||||
function filesystem.mount(cyka, path)
|
||||
if type(cyka) == "table" then
|
||||
for i = 1, #mountedProxies do
|
||||
if mountedProxies[i].path == path then
|
||||
return false, "mount path has been taken by other mounted filesystem"
|
||||
elseif mountedProxies[i].proxy == cyka then
|
||||
return false, "proxy is already mounted"
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(mountedProxies, {
|
||||
path = path,
|
||||
proxy = cyka
|
||||
})
|
||||
|
||||
return true
|
||||
else
|
||||
error("bad argument #1 (filesystem proxy expected, got " .. tostring(cyka) .. ")")
|
||||
end
|
||||
end
|
||||
|
||||
function filesystem.unmount(cyka)
|
||||
if type(cyka) == "table" then
|
||||
for i = 1, #mountedProxies do
|
||||
if mountedProxies[i].proxy == cyka then
|
||||
table.remove(mountedProxies, i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false, "specified proxy is not mounted"
|
||||
elseif type(cyka) == "string" then
|
||||
for i = 1, #mountedProxies do
|
||||
if mountedProxies[i].proxy.address == cyka then
|
||||
table.remove(mountedProxies, i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false, "specified proxy address is not mounted"
|
||||
else
|
||||
error("bad argument #1 (filesystem proxy or mounted path expected, got " .. tostring(cyka) .. ")")
|
||||
end
|
||||
end
|
||||
|
||||
function filesystem.get(path)
|
||||
checkArg(1, path, "string")
|
||||
|
||||
for i = 1, #mountedProxies do
|
||||
if path:sub(1, unicode.len(mountedProxies[i].path)) == mountedProxies[i].path then
|
||||
return mountedProxies[i].proxy, unicode.sub(path, mountedProxies[i].path:len() + 1, -1)
|
||||
end
|
||||
end
|
||||
|
||||
return BOOT_PROXY, path
|
||||
end
|
||||
|
||||
function filesystem.mounts()
|
||||
local key, value
|
||||
return function()
|
||||
key, value = next(mountedProxies, key)
|
||||
if value then
|
||||
return value.proxy, value.path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------- I/O methods -----------------------------------------
|
||||
|
||||
local function readAndReposition(self, count)
|
||||
local data = self.proxy.read(self.stream, count)
|
||||
if data then
|
||||
self.position = self.position + #data
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
local function readString(self, count)
|
||||
if count > #self.buffer then
|
||||
local data, chunk = self.buffer
|
||||
while #data < count do
|
||||
chunk = readAndReposition(self, BUFFER_SIZE)
|
||||
|
||||
if chunk then
|
||||
data = data .. chunk
|
||||
else
|
||||
self.buffer = ""
|
||||
-- EOF at start
|
||||
if data == "" then
|
||||
return nil
|
||||
-- EOF after read
|
||||
else
|
||||
return data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.buffer = data:sub(count + 1, -1)
|
||||
|
||||
return data:sub(1, count)
|
||||
else
|
||||
local data = self.buffer:sub(1, count)
|
||||
self.buffer = self.buffer:sub(count + 1, -1)
|
||||
|
||||
return data
|
||||
end
|
||||
end
|
||||
|
||||
local function readLine(self)
|
||||
local data = ""
|
||||
while true do
|
||||
if #self.buffer > 0 then
|
||||
local starting, ending = self.buffer:find("\n")
|
||||
if starting then
|
||||
data = data .. self.buffer:sub(1, starting - 1)
|
||||
self.buffer = self.buffer:sub(ending + 1, -1)
|
||||
|
||||
return data
|
||||
else
|
||||
data = data .. self.buffer
|
||||
end
|
||||
end
|
||||
|
||||
local chunk = readAndReposition(self, BUFFER_SIZE)
|
||||
if chunk then
|
||||
self.buffer = chunk
|
||||
-- EOF
|
||||
else
|
||||
local data = self.buffer
|
||||
self.buffer = ""
|
||||
|
||||
return #data > 0 and data or nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function lines(self)
|
||||
return function()
|
||||
local line = readLine(self)
|
||||
if line then
|
||||
return line
|
||||
else
|
||||
self:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function readAll(self)
|
||||
local data, chunk = ""
|
||||
while true do
|
||||
chunk = readAndReposition(self, 4096)
|
||||
if chunk then
|
||||
data = data .. chunk
|
||||
else
|
||||
return data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function readBytes(self, count, littleEndian)
|
||||
if count == 1 then
|
||||
local data = readString(self, 1)
|
||||
if data then
|
||||
return string.byte(data)
|
||||
end
|
||||
|
||||
return nil
|
||||
else
|
||||
local bytes, result = {string.byte(readString(self, count) or "\x00", 1, 8)}, 0
|
||||
|
||||
if littleEndian then
|
||||
for i = #bytes, 1, -1 do
|
||||
result = bit32.bor(bit32.lshift(result, 8), bytes[i])
|
||||
end
|
||||
else
|
||||
for i = 1, #bytes do
|
||||
result = bit32.bor(bit32.lshift(result, 8), bytes[i])
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
local function readUnicodeChar(self)
|
||||
local byteArray = {string.byte(readString(self, 1))}
|
||||
|
||||
local nullBitPosition = 0
|
||||
for i = 1, 7 do
|
||||
if bit32.band(bit32.rshift(byteArray[1], 8 - i), 0x1) == 0x0 then
|
||||
nullBitPosition = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, nullBitPosition - 2 do
|
||||
table.insert(byteArray, string.byte(readString(self, 1)))
|
||||
end
|
||||
|
||||
return string.char(table.unpack(byteArray))
|
||||
end
|
||||
|
||||
local function read(self, format, ...)
|
||||
local formatType = type(format)
|
||||
if formatType == "number" then
|
||||
return readChar(self, format)
|
||||
elseif formatType == "string" then
|
||||
format = format:gsub("^%*", "")
|
||||
|
||||
if format == "a" then
|
||||
return readAll(self)
|
||||
elseif format == "l" then
|
||||
return readLine(self)
|
||||
elseif format == "b" then
|
||||
return readByte(self)
|
||||
elseif format == "bs" then
|
||||
return readBytes(self, ...)
|
||||
elseif format == "u" then
|
||||
return readUnicodeChar(self)
|
||||
else
|
||||
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 .. ")")
|
||||
end
|
||||
else
|
||||
error("bad argument #1 (number or string expected, got " .. formatType ..")")
|
||||
end
|
||||
end
|
||||
|
||||
local function seek(self, pizda, cyka)
|
||||
if pizda == "set" then
|
||||
local value = self.proxy.seek(self.stream, "set", -self.position + cyka)
|
||||
self.position = cyka
|
||||
self.buffer = ""
|
||||
|
||||
return value
|
||||
elseif pizda == "cur" then
|
||||
local value = self.proxy.seek(self.stream, "cur", cyka)
|
||||
self.position = self.position + cyka
|
||||
self.buffer = ""
|
||||
|
||||
return value
|
||||
elseif pizda == "end" then
|
||||
local value = self.proxy.seek(self.stream, "set", self.size - 1)
|
||||
self.position = self.size - 1
|
||||
self.buffer = ""
|
||||
|
||||
return value
|
||||
else
|
||||
error("bad argument #2 ('set', 'cur' or 'end' expected, got " .. tostring(whence) .. ")")
|
||||
end
|
||||
end
|
||||
|
||||
local function write(self, ...)
|
||||
local data = {...}
|
||||
for i = 1, #data do
|
||||
data[i] = tostring(data[i])
|
||||
end
|
||||
data = table.concat(data)
|
||||
|
||||
-- Data is small enough to fit buffer
|
||||
if #data < (BUFFER_SIZE - #self.buffer) then
|
||||
self.buffer = self.buffer .. data
|
||||
|
||||
return true
|
||||
else
|
||||
-- Write current buffer content
|
||||
local success, reason = self.proxy.write(self.stream, self.buffer)
|
||||
if success then
|
||||
-- If data will not fit buffer, use iterative writing with data partitioning
|
||||
if #data > BUFFER_SIZE then
|
||||
for i = 1, #data, BUFFER_SIZE do
|
||||
success, reason = self.proxy.write(self.stream, data:sub(i, i + BUFFER_SIZE - 1))
|
||||
|
||||
if not success then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self.buffer = ""
|
||||
|
||||
return success, reason
|
||||
-- Data will perfectly fit in empty buffer
|
||||
else
|
||||
self.buffer = data
|
||||
|
||||
return true
|
||||
end
|
||||
else
|
||||
return false, reason
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function writeBytes(self, ...)
|
||||
return write(self, string.char(...))
|
||||
end
|
||||
|
||||
local function close(self)
|
||||
if self.write and #self.buffer > 0 then
|
||||
self.proxy.write(self.stream, self.buffer)
|
||||
end
|
||||
|
||||
return self.proxy.close(self.stream)
|
||||
end
|
||||
|
||||
function filesystem.open(path, mode)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
local result, reason = proxy.open(proxyPath, mode)
|
||||
if result then
|
||||
local handle = {
|
||||
proxy = proxy,
|
||||
stream = result,
|
||||
position = 0,
|
||||
buffer = "",
|
||||
close = close,
|
||||
seek = seek,
|
||||
}
|
||||
|
||||
if mode == "r" or mode == "rb" then
|
||||
handle.size = proxy.size(proxyPath)
|
||||
handle.readString = readString
|
||||
handle.readUnicodeChar = readUnicodeChar
|
||||
handle.readBytes = readBytes
|
||||
handle.readLine = readLine
|
||||
handle.lines = lines
|
||||
handle.readAll = readAll
|
||||
handle.read = read
|
||||
|
||||
return handle
|
||||
elseif mode == "w" or mode == "wb" or mode == "a" or mode == "ab" then
|
||||
handle.write = write
|
||||
handle.writeBytes = writeBytes
|
||||
|
||||
return handle
|
||||
else
|
||||
error("bad argument #2 ('r', 'rb', 'w', 'wb' or 'a' expected, got )" .. tostring(mode) .. ")")
|
||||
end
|
||||
else
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------- Rest proxy methods -----------------------------------------
|
||||
|
||||
function filesystem.exists(path)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
return proxy.exists(proxyPath)
|
||||
end
|
||||
|
||||
function filesystem.size(path)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
return proxy.size(proxyPath)
|
||||
end
|
||||
|
||||
function filesystem.isDirectory(path)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
return proxy.isDirectory(proxyPath)
|
||||
end
|
||||
|
||||
function filesystem.makeDirectory(path)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
return proxy.makeDirectory(proxyPath)
|
||||
end
|
||||
|
||||
function filesystem.lastModified(path)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
return proxy.lastModified(proxyPath)
|
||||
end
|
||||
|
||||
function filesystem.remove(path)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
return proxy.remove(proxyPath)
|
||||
end
|
||||
|
||||
function filesystem.rename(path, newPath)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
return proxy.rename(proxyPath, newPath)
|
||||
end
|
||||
|
||||
function filesystem.list(path, sortingMethod)
|
||||
local proxy, proxyPath = filesystem.get(path)
|
||||
|
||||
local list, reason = proxy.list(proxyPath)
|
||||
if list then
|
||||
-- Fullfill list with mounted paths if needed
|
||||
for i = 1, #mountedProxies do
|
||||
if path == filesystem.path(mountedProxies[i].path) then
|
||||
table.insert(list, filesystem.name(mountedProxies[i].path) .. "/")
|
||||
end
|
||||
end
|
||||
|
||||
-- Applying sorting methods
|
||||
if not sortingMethod or sortingMethod == filesystem.SORTING_NAME then
|
||||
table.sort(list, function(a, b)
|
||||
return unicode.lower(a) < unicode.lower(b)
|
||||
end)
|
||||
|
||||
return list
|
||||
elseif sortingMethod == filesystem.SORTING_DATE then
|
||||
table.sort(list, function(a, b)
|
||||
return filesystem.lastModified(path .. a) > filesystem.lastModified(path .. b)
|
||||
end)
|
||||
|
||||
return list
|
||||
elseif sortingMethod == filesystem.SORTING_TYPE then
|
||||
-- Creating a map with "extension" = {file1, file2, ...} structure
|
||||
local map, extension = {}
|
||||
for i = 1, #list do
|
||||
extension = filesystem.extension(list[i]) or "Z"
|
||||
|
||||
-- If it's a directory without extension
|
||||
if extension:sub(1, 1) ~= "." and filesystem.isDirectory(path .. list[i]) then
|
||||
extension = "."
|
||||
end
|
||||
|
||||
map[extension] = map[extension] or {}
|
||||
table.insert(map[extension], list[i])
|
||||
end
|
||||
|
||||
-- Sorting lists for each extension
|
||||
local extensions = {}
|
||||
for key, value in pairs(map) do
|
||||
table.sort(value, function(a, b)
|
||||
return unicode.lower(a) < unicode.lower(b)
|
||||
end)
|
||||
|
||||
table.insert(extensions, key)
|
||||
end
|
||||
|
||||
-- Sorting extensions
|
||||
table.sort(extensions, function(a, b)
|
||||
return unicode.lower(a) < unicode.lower(b)
|
||||
end)
|
||||
|
||||
-- Fullfilling final list
|
||||
list = {}
|
||||
for i = 1, #extensions do
|
||||
for j = 1, #map[extensions[i]] do
|
||||
table.insert(list, map[extensions[i]][j])
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
end
|
||||
|
||||
return list, reason
|
||||
end
|
||||
|
||||
--------------------------------------- Advanced methods -----------------------------------------
|
||||
|
||||
function filesystem.copy(from, to)
|
||||
local fromHandle, reason = filesystem.open(from, "rb")
|
||||
if fromHandle then
|
||||
local toHandle, reason = filesystem.open(to, "wb")
|
||||
if toHandle then
|
||||
while true do
|
||||
local chunk = readString(fromHandle, BUFFER_SIZE)
|
||||
if chunk then
|
||||
local success, reason = write(toHandle, chunk)
|
||||
|
||||
if not success then
|
||||
return false, reason
|
||||
end
|
||||
else
|
||||
toHandle:close()
|
||||
fromHandle:close()
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
return false, reason
|
||||
end
|
||||
else
|
||||
return false, reason
|
||||
end
|
||||
end
|
||||
|
||||
function filesystem.read(path)
|
||||
local handle, reason = filesystem.open(path, "rb")
|
||||
if handle then
|
||||
local data = readAll(handle)
|
||||
handle:close()
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
return false, reason
|
||||
end
|
||||
|
||||
function filesystem.lines(path)
|
||||
local handle, reason = filesystem.open(path, "rb")
|
||||
if handle then
|
||||
return handle:lines()
|
||||
else
|
||||
error(reason)
|
||||
end
|
||||
end
|
||||
|
||||
function filesystem.readLines(path)
|
||||
local handle, reason = filesystem.open(path, "rb")
|
||||
if handle then
|
||||
local lines, index, line = {}, 1
|
||||
|
||||
repeat
|
||||
line = readLine(handle)
|
||||
lines[index] = line
|
||||
index = index + 1
|
||||
until not line
|
||||
|
||||
handle:close()
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
return false, reason
|
||||
end
|
||||
|
||||
local function writeOrAppend(append, path, ...)
|
||||
filesystem.makeDirectory(filesystem.path(path))
|
||||
|
||||
local handle, reason = filesystem.open(path, append and "ab" or "wb")
|
||||
if handle then
|
||||
local result, reason = write(handle, ...)
|
||||
handle:close()
|
||||
|
||||
return result, reason
|
||||
end
|
||||
|
||||
return false, reason
|
||||
end
|
||||
|
||||
function filesystem.write(path, ...)
|
||||
return writeOrAppend(false, path,...)
|
||||
end
|
||||
|
||||
function filesystem.append(path, ...)
|
||||
return writeOrAppend(true, path, ...)
|
||||
end
|
||||
|
||||
function filesystem.writeTable(path, ...)
|
||||
return filesystem.write(path, require("Text").serialize(...))
|
||||
end
|
||||
|
||||
function filesystem.readTable(path)
|
||||
local result, reason = filesystem.read(path)
|
||||
if result then
|
||||
return require("Text").deserialize(result)
|
||||
end
|
||||
|
||||
return result, reason
|
||||
end
|
||||
|
||||
function filesystem.setProxy(proxy)
|
||||
BOOT_PROXY = proxy
|
||||
end
|
||||
|
||||
function filesystem.getProxy()
|
||||
return BOOT_PROXY
|
||||
end
|
||||
|
||||
--------------------------------------- loadfile() and dofile() implementation -----------------------------------------
|
||||
|
||||
function loadfile(path)
|
||||
local data, reason = filesystem.read(path)
|
||||
if data then
|
||||
return load(data, "=" .. path)
|
||||
end
|
||||
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
function dofile(path, ...)
|
||||
local result, reason = loadfile(path)
|
||||
if result then
|
||||
local data = {xpcall(result, debug.traceback, ...)}
|
||||
if data[1] then
|
||||
return table.unpack(data, 2)
|
||||
else
|
||||
error(data[2])
|
||||
end
|
||||
else
|
||||
error(reason)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Mount all existing filesystem components
|
||||
for address in component.list("filesystem") do
|
||||
filesystem.mount(component.proxy(address), paths.system.mounts .. address .. "/")
|
||||
end
|
||||
|
||||
-- Automatically mount/unmount filesystem components
|
||||
event.addHandler(function(signal, address, type)
|
||||
if signal == "component_added" and type == "filesystem" then
|
||||
filesystem.mount(component.proxy(address), paths.system.mounts .. address .. "/")
|
||||
elseif signal == "component_removed" and type == "filesystem" then
|
||||
filesystem.unmount(address)
|
||||
end
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
return filesystem
|
||||
Reference in New Issue
Block a user