Merge branch 'unfinished-ag2' into Pre-Alpha-3.0.0
This commit is contained in:
+23
-20
@@ -32,6 +32,11 @@ function filesystem.canonical(path)
|
||||
return "/" .. table.concat(segList, "/")
|
||||
end
|
||||
|
||||
function filesystem.basename(path)
|
||||
checkArg(1, path, "string")
|
||||
return path:match("/([^/]+)/?$") or ""
|
||||
end
|
||||
|
||||
function filesystem.concat(path1, path2)
|
||||
checkArg(1, path1, "string")
|
||||
checkArg(2, path2, "string")
|
||||
@@ -67,6 +72,13 @@ function filesystem.absolutePath(path) -- returns the address and absolute path
|
||||
return address, path
|
||||
end
|
||||
|
||||
function filesystem.parent(path)
|
||||
checkArg(1, path, "string")
|
||||
local p = filesystem.canonical(path)
|
||||
-- return "/" on "/"
|
||||
return p == "/" and "/" or (p:match("^(.*)/[^/]+/?$") or "/")
|
||||
end
|
||||
|
||||
function filesystem.exists(path) -- check if path exists
|
||||
checkArg(1, path, "string")
|
||||
local address, absPath = filesystem.absolutePath(path)
|
||||
@@ -350,7 +362,7 @@ function filesystem.open(path, mode, buffered) -- opens a file and returns its h
|
||||
end
|
||||
end
|
||||
function properHandle.seek(self, whence, offset)
|
||||
checkArg(2, whence, "string", "number")
|
||||
checkArg(2, whence, "string", "number", "nil")
|
||||
checkArg(3, offset, "number", "nil")
|
||||
if not offset then
|
||||
offset = 0
|
||||
@@ -475,14 +487,12 @@ local function copyContent(fromHandle, toHandle)
|
||||
if not (fromHandle and toHandle) then
|
||||
return
|
||||
end
|
||||
local memory = math.floor(computer.freeMemory() * 0.8)
|
||||
local tmpdata
|
||||
while true do
|
||||
tmpdata = fromHandle:read(memory)
|
||||
local tmpdata = fromHandle.read(2048)
|
||||
if not tmpdata then
|
||||
break
|
||||
end
|
||||
local status, reason = toHandle:write(tmpdata)
|
||||
local status, reason = toHandle.write(tmpdata)
|
||||
if status ~= true then
|
||||
break
|
||||
end
|
||||
@@ -492,7 +502,6 @@ local function copyContent(fromHandle, toHandle)
|
||||
end
|
||||
|
||||
local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
|
||||
-- TODO: make this use copyContent
|
||||
if fromAbsPath:sub(-1) == "/" then
|
||||
fromAbsPath = fromAbsPath:sub(1, -2)
|
||||
end
|
||||
@@ -506,31 +515,21 @@ local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
|
||||
end
|
||||
for i = 1, #fileList do
|
||||
local fromFile, toFile = fromAbsPath .. "/" .. fileList[i], toAbsPath .. "/" .. fileList[i]
|
||||
--[[ local handle = component.invoke(fromAddress, "open", fromFile, "r")
|
||||
local data, tmpdata = "", nil
|
||||
repeat
|
||||
tmpdata = component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger)
|
||||
data = data .. (tmpdata or "")
|
||||
until not tmpdata
|
||||
tmpdata = component.invoke(fromAddress, "close", handle)
|
||||
local handle = component.invoke(toAddress, "open", toFile, "w")
|
||||
component.invoke(toAddress, "write", handle, data)
|
||||
component.invoke(toAddress, "close", handle) ]]
|
||||
local fromHandle = component.invoke(fromAddress, "open", fromFile, "r")
|
||||
local toHandle = component.invoke(toAddress, "open", toFile, "w")
|
||||
copyContent({
|
||||
["read"] = function(...)
|
||||
return component.invoke(fromAddress, "read", handle, ...)
|
||||
return component.invoke(fromAddress, "read", fromHandle, ...)
|
||||
end,
|
||||
["close"] = function(...)
|
||||
return component.invoke(fromAddress, "close", handle, ...)
|
||||
return component.invoke(fromAddress, "close", fromHandle, ...)
|
||||
end,
|
||||
}, {
|
||||
["write"] = function(...)
|
||||
return component.invoke(fromAddress, "write", handle, ...)
|
||||
return component.invoke(toAddress, "write", toHandle, ...)
|
||||
end,
|
||||
["close"] = function(...)
|
||||
return component.invoke(fromAddress, "close", handle, ...)
|
||||
return component.invoke(toAddress, "close", toHandle, ...)
|
||||
end,
|
||||
})
|
||||
end
|
||||
@@ -545,6 +544,10 @@ function filesystem.isDirectory(path)
|
||||
return component.invoke(address, "isDirectory", absPath)
|
||||
end
|
||||
|
||||
function filesystem.isFile(path)
|
||||
return not filesystem.isDirectory(path) and filesystem.exists(path)
|
||||
end
|
||||
|
||||
function filesystem.rename(fromPath, toPath)
|
||||
checkArg(1, fromPath, "string")
|
||||
checkArg(2, toPath, "string")
|
||||
|
||||
+4
-7
@@ -55,14 +55,11 @@ local logFileSizeLimit = 16384
|
||||
|
||||
local function writeToLog(path, text)
|
||||
fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories
|
||||
local handle
|
||||
if fs.exists(path) then
|
||||
handle = assert(fs.open(path, "a"))
|
||||
else
|
||||
handle = assert(fs.open(path, "w"))
|
||||
local handle = fs.open(path, "a")
|
||||
if handle then
|
||||
handle:write(text .. "\n")
|
||||
handle:close()
|
||||
end
|
||||
handle:write(text .. "\n")
|
||||
handle:close()
|
||||
|
||||
-- Log trimming if it gets too long
|
||||
if fs.size(path) > logFileSizeLimit then
|
||||
|
||||
+43
-122
@@ -1,127 +1,48 @@
|
||||
local serialize = {}
|
||||
|
||||
function serialize.string(str)
|
||||
return '"'..str:gsub("[%z\1-\31\34\92\127-\159]",function(c)
|
||||
local byte = c:byte()
|
||||
if byte== 7 then return "\\a" end
|
||||
if byte== 8 then return "\\b" end
|
||||
if byte== 9 then return "\\t" end
|
||||
if byte==10 then return "\\n" end
|
||||
if byte==11 then return "\\v" end
|
||||
if byte==12 then return "\\f" end
|
||||
if byte==13 then return "\\r" end
|
||||
if byte==34 then return "\\\"" end
|
||||
if byte==92 then return "\\\\" end
|
||||
return string.format("\\x%02x",byte)
|
||||
end)..'"'
|
||||
local function _serialize(value, indent, level, visited)
|
||||
local currentIndent = indent and string.rep(indent, level) or ""
|
||||
local nextIndent = indent and string.rep(indent, level + 1) or ""
|
||||
local sep = indent and "\n" or " "
|
||||
local t = type(value)
|
||||
if t == "nil" then return "nil" end
|
||||
if t == "string" then return string.format("%q", value) end
|
||||
if t == "number" then
|
||||
if value ~= value then return "0/0" end
|
||||
if value == math.huge then return "math.huge" end
|
||||
if value == -math.huge then return "-math.huge" end
|
||||
return tostring(value)
|
||||
end
|
||||
if t == "boolean" then return tostring(value) end
|
||||
if t == "table" then
|
||||
if visited[value] then return "..." end
|
||||
visited[value] = true
|
||||
local items = {}
|
||||
local arrayCount = 0
|
||||
for i = 1, #value do
|
||||
if value[i] ~= nil then arrayCount = i else break end
|
||||
end
|
||||
for i = 1, arrayCount do
|
||||
table.insert(items, nextIndent .. _serialize(value[i], indent, level + 1, visited))
|
||||
end
|
||||
for k, v in pairs(value) do
|
||||
if type(k) ~= "number" or k < 1 or k > arrayCount then
|
||||
local keyStr
|
||||
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
|
||||
keyStr = k
|
||||
else
|
||||
keyStr = "[" .. _serialize(k, indent, level + 1, visited) .. "]"
|
||||
end
|
||||
table.insert(items, nextIndent .. keyStr .. " = " .. _serialize(v, indent, level + 1, visited))
|
||||
end
|
||||
end
|
||||
visited[value] = nil
|
||||
if #items == 0 then return "{}" end
|
||||
return "{" .. sep .. table.concat(items, "," .. sep) .. sep .. currentIndent .. "}"
|
||||
end
|
||||
return tostring(value)
|
||||
end
|
||||
|
||||
function serialize.table(tbl,colors,stack)
|
||||
stack = table.copy(stack or {})
|
||||
table.insert(stack,tbl)
|
||||
local keyAmount = 0
|
||||
local keyNumber = true
|
||||
local out = ""
|
||||
local first = true
|
||||
|
||||
for key,val in pairs(tbl) do
|
||||
if not first then out=out..",\n" end
|
||||
first=false
|
||||
out=out.." "
|
||||
if type(key)=="string" then
|
||||
if key:match("^[%a_][%w_]*$") then
|
||||
out=out..key.."="
|
||||
else
|
||||
out=out..'['..serialize.string(key)..']='
|
||||
end
|
||||
else
|
||||
out=out.."["..tostring(key).."]="
|
||||
end
|
||||
if type(key)~="number" then
|
||||
keyNumber=false
|
||||
end
|
||||
|
||||
local success,reason = pcall(function()
|
||||
local valStr = ""
|
||||
if type(val)=="table" then
|
||||
if #stack>4 or table.find(stack,val) then
|
||||
valStr="..."
|
||||
else
|
||||
valStr=serialize.table(val,colors,stack)
|
||||
end
|
||||
elseif type(val)=="string" then
|
||||
local lines = {}
|
||||
for line in (val.."\n"):gmatch("([^\n]*)\n") do table.insert(lines,line) end
|
||||
if #lines[#lines]==0 then
|
||||
lines[#lines]=nil
|
||||
lines[#lines]=lines[#lines].."\n"
|
||||
end
|
||||
for i=1,#lines do
|
||||
if i<#lines then
|
||||
lines[i]=serialize.string(lines[i].."\n")
|
||||
else
|
||||
lines[i]=serialize.string(lines[i])
|
||||
end
|
||||
end
|
||||
valStr=table.concat(lines," ..\n ")
|
||||
else
|
||||
valStr=tostring(val)
|
||||
end
|
||||
local lines = {}
|
||||
for line in (valStr.."\n"):gmatch("([^\n]*)\n") do table.insert(lines,line) end
|
||||
out=out..table.concat(lines,"\n ")
|
||||
lines = nil
|
||||
keyAmount=keyAmount+1
|
||||
end)
|
||||
if not success then
|
||||
if colors then out=out.."\x1b[91m" end
|
||||
out=out.."["..tostring(reason).."]"
|
||||
if colors then out=out.."\x1b[39m" end
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
local metatbl = getmetatable(tbl)
|
||||
local metakeys = {}
|
||||
local metastring = ""
|
||||
if type(metatbl)=="table" then
|
||||
for i,v in pairs(metatbl) do
|
||||
keyNumber=false
|
||||
table.insert(metakeys,i)
|
||||
end
|
||||
end
|
||||
if #metakeys>0 then
|
||||
out=out.."\n "
|
||||
if colors then metastring=metastring.."\x1b[92m" end
|
||||
if table.find(metakeys,"__tostring") then
|
||||
metastring=metastring.."tostring: "..serialize.string(tostring(tbl)).."\n "
|
||||
table.remove(metakeys,table.find(metakeys,"__tostring"))
|
||||
end
|
||||
metastring=metastring..table.concat(metakeys,", ")
|
||||
if colors then metastring=metastring.."\x1b[39m" end
|
||||
out=out..metastring
|
||||
end
|
||||
|
||||
if keyAmount==0 then return "{"..metastring.."}" end
|
||||
if keyNumber then
|
||||
-- fix strings not being serialised
|
||||
local vals = {}
|
||||
for _,v in pairs(tbl) do
|
||||
if #vals>=5 and #stack>1 then
|
||||
table.insert(vals,"...")
|
||||
break
|
||||
end
|
||||
if type(v)=="table" then
|
||||
table.insert(vals,serialize.table(v,colors,stack))
|
||||
elseif type(v)=="string" then
|
||||
table.insert(vals,serialize.string(v))
|
||||
else
|
||||
table.insert(vals,tostring(v))
|
||||
end
|
||||
end
|
||||
return "{"..table.concat(vals,", ")..(#metakeys>0 and "\n "..metastring or "").."}"
|
||||
end
|
||||
return "{\n"..out.."\n}"
|
||||
function serialize(value, indent)
|
||||
return _serialize(value, indent, 0, {})
|
||||
end
|
||||
|
||||
return serialize
|
||||
|
||||
+501
@@ -0,0 +1,501 @@
|
||||
local defaultDBPath = "/ag2/testdb.json"
|
||||
|
||||
local computer = require("computer")
|
||||
local fs = require("filesystem")
|
||||
|
||||
-- local db = require("solvitdb")
|
||||
local db = {}
|
||||
local json = require("json")
|
||||
function db.create(dbpath)
|
||||
local handle = fs.open(dbpath,"w")
|
||||
handle:write("{}")
|
||||
handle:close()
|
||||
end
|
||||
function db.readJSON(dbpath)
|
||||
local handle = fs.open(dbpath,"r")
|
||||
local content = ""
|
||||
while true do
|
||||
local s = handle:read(math.huge or math.maxinteger)
|
||||
if not s then break end
|
||||
content=content..s
|
||||
end
|
||||
handle:close()
|
||||
return content
|
||||
end
|
||||
function db.get(dbpath,pack)
|
||||
local dbc = json.decode(db.readJSON(dbpath))
|
||||
return dbc[pack]
|
||||
end
|
||||
function db.set(dbpath,pack,info)
|
||||
local dbc = json.decode(db.readJSON(dbpath))
|
||||
dbc[pack]=info
|
||||
local handle = fs.open(dbpath,"w")
|
||||
handle:write(json.encode(dbc))
|
||||
handle:close()
|
||||
end
|
||||
function db.remove(dbpath,pack)
|
||||
local dbc = json.decode(db.readJSON(dbpath))
|
||||
dbc[pack]=nil
|
||||
local handle = fs.open(dbpath,"w")
|
||||
handle:write(json.encode(dbc))
|
||||
handle:close()
|
||||
end
|
||||
function db.list(dbpath,pack)
|
||||
local dbc = json.decode(db.readJSON(dbpath))
|
||||
local keys = {}
|
||||
for i,_ in pairs(dbc) do
|
||||
table.insert(keys,i)
|
||||
end
|
||||
return ipairs(keys)
|
||||
end
|
||||
|
||||
local avs = {}
|
||||
function avs.splitSingular(s)
|
||||
local result = {}
|
||||
for str in string.gmatch(s, "([^.]+)") do
|
||||
table.insert(result,tonumber(str) or -1)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function avs.parse(pack)
|
||||
if not string.find(pack,"=") then
|
||||
return {pack}
|
||||
end
|
||||
local idx=pack:find("=")
|
||||
local name=pack:sub(1,idx-1)
|
||||
local verstr=pack:sub(idx+1)
|
||||
if string.find(verstr,"-") then
|
||||
idx=verstr:find("-")
|
||||
verstr={verstr:sub(1,idx-1),verstr:sub(idx+1)}
|
||||
else
|
||||
verstr={verstr}
|
||||
end
|
||||
for i=1,#verstr do
|
||||
verstr[i]=avs.splitSingular(verstr[i])
|
||||
end
|
||||
if #verstr>1 then
|
||||
for i=1,3 do
|
||||
verstr[1][i]=math.max(verstr[1][i],0)
|
||||
end
|
||||
end
|
||||
return {name,verstr}
|
||||
end
|
||||
|
||||
function avs.serializeSingle(ver)
|
||||
local ver2 = table.copy(ver)
|
||||
for i=1,3 do
|
||||
if ver2[i]==-1 then
|
||||
ver2[i]="*"
|
||||
else
|
||||
ver2[i]=tostring(ver2[i])
|
||||
end
|
||||
end
|
||||
return ver2[1].."."..ver2[2].."."..ver2[3]
|
||||
end
|
||||
|
||||
function avs.serializeVersion(ver)
|
||||
local singles = {}
|
||||
for i=1,#ver do
|
||||
table.insert(singles,avs.serializeSingle(ver[i]))
|
||||
end
|
||||
if singles[1]==singles[2] then
|
||||
singles={singles[1]}
|
||||
end
|
||||
local out=""
|
||||
for i=1,#singles do
|
||||
out=out..singles[i]
|
||||
if i~=#singles then
|
||||
out=out.."-"
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
function avs.serializePack(pack)
|
||||
if #pack==1 then
|
||||
return pack[1]
|
||||
end
|
||||
return pack[1].."="..avs.serializeVersion(pack[2])
|
||||
end
|
||||
|
||||
function avs.singleGreater(ver1,ver2)
|
||||
for i=1,3 do
|
||||
if ver1[i]~=ver2[i] then
|
||||
if ver1[i]==-1 or ver1[i]>ver2[i] then
|
||||
return true
|
||||
end
|
||||
if ver2[i]==-1 or ver2[i]>ver1[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function avs.singleLesser(ver1,ver2)
|
||||
return avs.singleGreater(ver2,ver1)
|
||||
end
|
||||
|
||||
function avs.singleMin(ver1,ver2)
|
||||
return avs.singleLesser(ver1,ver2) and ver1 or ver2
|
||||
end
|
||||
|
||||
function avs.singleMax(ver1,ver2)
|
||||
return avs.singleGreater(ver1,ver2) and ver1 or ver2
|
||||
end
|
||||
|
||||
function avs.compatibleRange(vers)
|
||||
for i=1,#vers do
|
||||
if type(vers[i])=="string" then vers[i]=avs.parse(vers[i])[2] end
|
||||
if #vers[i]==1 then vers[i]={vers[i][1],vers[i][1]} end
|
||||
end
|
||||
local range = vers[1]
|
||||
for i=2,#vers do
|
||||
range[1]=avs.singleMax(range[1],vers[i][1])
|
||||
range[2]=avs.singleMin(range[2],vers[i][2])
|
||||
end
|
||||
if avs.singleGreater(range[1],range[2]) then
|
||||
return nil
|
||||
end
|
||||
return range
|
||||
end
|
||||
|
||||
function avs.matching(pack1,pack2)
|
||||
if pack1[1]~=pack2[1] then return false end
|
||||
local ver1 = pack1[2] or {{-1,-1,-1}}
|
||||
local ver2 = pack2[2] or {{-1,-1,-1}}
|
||||
if #ver1==1 then ver1={ver1[1],ver1[1]} end
|
||||
if #ver2==1 then ver2={ver2[1],ver2[1]} end
|
||||
return avs.compatibleRange({ver1,ver2})~=nil
|
||||
end
|
||||
|
||||
local function packageInArray(pack,arr)
|
||||
for i=1,#arr do
|
||||
if avs.matching(avs.parse(arr[i]),pack) then
|
||||
return true,arr[i]
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function packageNameInArray(pack,arr)
|
||||
for i=1,#arr do
|
||||
if arr[i][1]==pack[1] then
|
||||
return true,arr[i]
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function removeFromArray(el,arr)
|
||||
for i=1,#arr do
|
||||
if arr[i]==el then
|
||||
table.remove(arr,i)
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function startTransaction(dbpath)
|
||||
dbpath = dbpath or defaultDBPath
|
||||
if not fs.exists(dbpath) then
|
||||
db.create(dbpath)
|
||||
end
|
||||
|
||||
local yieldClock = computer.uptime()
|
||||
local function yieldIfNecessary()
|
||||
if computer.uptime()-yieldClock>=0.1 then
|
||||
coroutine.yield()
|
||||
yieldClock = computer.uptime()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local installIncomplete = false
|
||||
local removeIncomplete = false
|
||||
|
||||
local packInfo = {}
|
||||
local ins = {}
|
||||
local rem = {}
|
||||
local transaction = {}
|
||||
function transaction.install(name)
|
||||
table.insert(ins,avs.parse(name))
|
||||
installIncomplete = true
|
||||
end
|
||||
function transaction.remove(name)
|
||||
table.insert(rem,avs.parse(name))
|
||||
removeIncomplete = true
|
||||
end
|
||||
function transaction.autoRemove()
|
||||
end
|
||||
function transaction.update(name)
|
||||
end
|
||||
function transaction.updateAll(name)
|
||||
end
|
||||
function transaction.addInfo(name,info)
|
||||
if not info.type then info.type="package" end
|
||||
packInfo[name]=info
|
||||
-- print(require("serialize")(packInfo))
|
||||
end
|
||||
|
||||
local function getPackInfo(pack)
|
||||
return packInfo[avs.serializePack(pack)]
|
||||
end
|
||||
local function findReverseDependencies(pack)
|
||||
local out={}
|
||||
for i,info in pairs(packInfo) do
|
||||
if info.dependencies and packageInArray(pack,info.dependencies) then
|
||||
table.insert(out,avs.parse(i))
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
local function finalizeInstall(settings)
|
||||
-- find missing package information
|
||||
local missing = {}
|
||||
for i=1,#ins do
|
||||
if getPackInfo(ins[i])==nil then
|
||||
table.insert(missing,avs.serializePack(ins[i]))
|
||||
end
|
||||
end
|
||||
if #missing>0 then
|
||||
return false,missing
|
||||
end
|
||||
-- find dependencies
|
||||
installIncomplete=false
|
||||
local i=1
|
||||
while i<=#ins do
|
||||
local deps = getPackInfo(ins[i]).dependencies
|
||||
if deps and #deps>=1 then
|
||||
for j=1,#deps do
|
||||
local dep = avs.parse(deps[j])
|
||||
local inArr,arrPack = packageNameInArray(dep,ins)
|
||||
if inArr then
|
||||
if not avs.matching(dep,arrPack) then
|
||||
local msg = ""
|
||||
if settings.resolveConflict then
|
||||
msg="Cannot resolve conflict: "..msg
|
||||
end
|
||||
local rev = findReverseDependencies(arrPack)
|
||||
if #rev==0 then
|
||||
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but one or more packages depend on "..avs.serializePack(arrPack)
|
||||
return false, msg
|
||||
elseif #rev==1 then
|
||||
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but package "..avs.serializePack(rev[1]).." depends on "..avs.serializePack(arrPack)
|
||||
return false, msg
|
||||
else
|
||||
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but packages "
|
||||
for i=1,#rev do
|
||||
if i>1 and i~=#rev then
|
||||
msg=msg..", "
|
||||
elseif i==#rev then
|
||||
msg=msg.." and "
|
||||
end
|
||||
msg=msg..avs.serializePack(rev[i])
|
||||
end
|
||||
msg=msg.." depend on "..avs.serializePack(arrPack)
|
||||
return false, msg
|
||||
end
|
||||
end
|
||||
elseif type(db.get(dbpath,dep[1]))=="nil" then
|
||||
installIncomplete=true
|
||||
table.insert(ins,j,dep)
|
||||
else
|
||||
local dbinfo = db.get(dbpath,dep[1])
|
||||
local dbpack = {dep[1]}
|
||||
if dbinfo.version then
|
||||
dbpack = avs.parse(dep[1].."="..dbinfo.version)
|
||||
end
|
||||
if not avs.matching(dep,dbpack) then
|
||||
if settings.resolveConflict then
|
||||
removeIncomplete=true
|
||||
table.insert(rem,1,dbpack)
|
||||
|
||||
installIncomplete=true
|
||||
table.insert(ins,j,dep)
|
||||
else
|
||||
local msg = "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but another version ("..avs.serializePack(dbpack)..") is installed"
|
||||
return false, msg
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
i=i+#deps
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
local function finalizeRemove(settings)
|
||||
removeIncomplete=false
|
||||
-- filter to only have packages in the database
|
||||
local i=1
|
||||
while i<=#rem do
|
||||
local dat = db.get(dbpath,rem[i][1])
|
||||
if not dat then
|
||||
table.remove(rem,i)
|
||||
else
|
||||
packInfo[avs.serializePack(rem[i])]=dat
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
-- get package info from database
|
||||
for i=1,#rem do
|
||||
if not getPackInfo(rem[i]) then
|
||||
packInfo[avs.serializePack(rem[i])]=db.get(rem[i][1])
|
||||
end
|
||||
end
|
||||
-- find if the main package is reverse dependant to another
|
||||
i=1
|
||||
while i<=#rem do
|
||||
local dat = getPackInfo(rem[i])
|
||||
if dat.reverseDependencies and #dat.reverseDependencies>0 then
|
||||
for _,dep in ipairs(dat.reverseDependencies) do
|
||||
if not packageNameInArray({dep},rem) then
|
||||
if settings.cascade then
|
||||
table.insert(rem,1,{dep})
|
||||
i=i+1
|
||||
else
|
||||
return false, "Package "..rem[i][1].." is a dependency of "..dep
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
-- look for dependencies if settings.autoremove is on
|
||||
if settings.autoremove then
|
||||
i=1
|
||||
while i<=#rem do
|
||||
local deps = getPackInfo(rem[i]).dependencies
|
||||
if deps and #deps>=1 then
|
||||
for j=1,#deps do
|
||||
local dep = avs.parse(deps[j])
|
||||
local depdat = packInfo[deps[j]]
|
||||
if not depdat then
|
||||
depdat = db.get(dbpath,dep[1])
|
||||
packInfo[deps[j]]=depdat
|
||||
end
|
||||
if (not packageNameInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==rem[i][1]) then
|
||||
removeIncomplete=true
|
||||
table.insert(rem,j,dep)
|
||||
end
|
||||
end
|
||||
i=i+#deps
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
end
|
||||
function transaction.finalize(settings)
|
||||
settings = settings or {}
|
||||
|
||||
while installIncomplete or removeIncomplete do
|
||||
while installIncomplete do
|
||||
local out = {finalizeInstall(settings)}
|
||||
if out[1]==false then return table.unpack(out) end
|
||||
yieldIfNecessary()
|
||||
end
|
||||
while removeIncomplete do
|
||||
local out = {finalizeRemove(settings)}
|
||||
if out[1]==false then return table.unpack(out) end
|
||||
yieldIfNecessary()
|
||||
end
|
||||
end
|
||||
|
||||
local install = {}
|
||||
local remove = {}
|
||||
for i=1,#ins do
|
||||
table.insert(install,avs.serializePack(ins[i]))
|
||||
end
|
||||
for i=1,#rem do
|
||||
table.insert(remove,avs.serializePack(rem[i]))
|
||||
end
|
||||
return true, {install=install,remove=remove}
|
||||
|
||||
-- return "true, {["install"] = {"dep1", "package1", "package2"}, ["remove"] = {"package3"}}" on success
|
||||
-- return "false, {"dep1"}" when not enough data
|
||||
-- return "false, "[verbose string]"" when conflict found
|
||||
-- TODO: handle same range AVS
|
||||
-- TODO: handle different intercompatible 1.*.* range AVS
|
||||
-- TODO: handle different incompatible 1.*.* range AVS
|
||||
-- TODO: handle different intercompatible 1.*.*-2.*.* range AVS
|
||||
-- TODO: handle different incompatible 1.*.*-2.*.* range AVS
|
||||
-- TODO: handle conflicts from package info
|
||||
-- TODO: handle reverse conflicts from another package's info
|
||||
-- TODO: handle automatic conflict resolving
|
||||
-- TODO: handle update of a single package with no dependencies
|
||||
-- TODO: handle update of a single package with dependencies that don't need updating
|
||||
-- TODO: handle update of a single package with dependencies that need updating
|
||||
-- TODO: handle update of a single package that has a set dependency version changed
|
||||
-- TODO: handle updating all packages in the database
|
||||
-- TODO: handle installing optional packages
|
||||
-- TODO: handle installing virtual packages and store this vpack info to database
|
||||
-- TODO: handle removing virtual packages from database info
|
||||
-- TODO: handle installing groups and store this group info to database
|
||||
-- TODO: handle removing groups and store this group info to database
|
||||
end
|
||||
local function storeInstall()
|
||||
-- directly set
|
||||
for _,pack in ipairs(ins) do
|
||||
if getPackInfo(pack) then
|
||||
local info = table.copy(getPackInfo(pack))
|
||||
if pack[2] then
|
||||
info.version=avs.serializeVersion(pack[2])
|
||||
else
|
||||
info.version=info.latestVersion or info.version
|
||||
end
|
||||
db.set(dbpath,pack[1],info)
|
||||
end
|
||||
end
|
||||
-- set reverse dependencies
|
||||
for _,pack in pairs(ins) do
|
||||
local i = avs.serializePack(pack)
|
||||
local v = packInfo[i]
|
||||
if v and v.dependencies then
|
||||
for _,dep in ipairs(v.dependencies) do
|
||||
local depname = avs.parse(dep)[1]
|
||||
local dat = db.get(dbpath,depname)
|
||||
if not dat then goto continue end
|
||||
if type(dat.reverseDependencies)~="table" then
|
||||
dat.reverseDependencies={}
|
||||
end
|
||||
table.insert(dat.reverseDependencies,pack[1])
|
||||
db.set(dbpath,depname,dat)
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local function storeRemove()
|
||||
-- directly remove
|
||||
for _,pack in ipairs(rem) do
|
||||
db.remove(dbpath,pack[1])
|
||||
end
|
||||
-- remove reverse dependencies
|
||||
for _,pack in ipairs(rem) do
|
||||
local pdat = getPackInfo(pack)
|
||||
if not pdat.dependencies then goto continue end
|
||||
for _,dep in ipairs(pdat.dependencies) do
|
||||
local depname = avs.parse(dep)[1]
|
||||
local dat = db.get(dbpath,depname)
|
||||
if dat.reverseDependencies then
|
||||
removeFromArray(pack[1],dat.reverseDependencies)
|
||||
end
|
||||
db.set(dbpath,depname,dat)
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
function transaction.store()
|
||||
if #ins>0 then
|
||||
storeInstall()
|
||||
end
|
||||
if #rem>0 then
|
||||
storeRemove()
|
||||
end
|
||||
end
|
||||
return transaction
|
||||
end
|
||||
|
||||
return { avs=avs,startTransaction=startTransaction }
|
||||
@@ -0,0 +1,312 @@
|
||||
local solvitdb = {}
|
||||
|
||||
local fs = require("filesystem")
|
||||
|
||||
local function checkValidityAndOpen(path)
|
||||
local handle = assert(fs.open(path))
|
||||
local data = assert(handle:read(8))
|
||||
if data:sub(5, 8) == "RTFM" then
|
||||
local patLength = string.unpack("<I4", data:sub(1, 4))
|
||||
return handle, patLength
|
||||
else
|
||||
error("missing magic")
|
||||
end
|
||||
end
|
||||
|
||||
local function readPat(handle, patLength)
|
||||
-- This needs the handle to be at byte 4
|
||||
local data, tmpdata = ""
|
||||
repeat
|
||||
tmpdata = handle:read(patLength - #data)
|
||||
data = data .. (tmpdata or "")
|
||||
until not tmpdata
|
||||
local packages = {}
|
||||
for packageData in data:gmatch("(.-%.....);") do
|
||||
packages[packageData:sub(1, -6)] = string.unpack("<I4", packageData:sub(-4, -1))
|
||||
end
|
||||
return packages
|
||||
end
|
||||
|
||||
local function insert(filePath, location, bytes)
|
||||
local chunkLength = 512
|
||||
local readHandle = assert(fs.open(filePath, "r"))
|
||||
local tmpFilePath = filePath .. ".tmp"
|
||||
local writeHandle = assert(fs.open(tmpFilePath, "w"))
|
||||
local i = 0
|
||||
while true do
|
||||
local readAmount = chunkLength
|
||||
if readAmount > location - i then
|
||||
readAmount = location - i
|
||||
end
|
||||
if readAmount == 0 then
|
||||
break
|
||||
end
|
||||
local data = readHandle:read(readAmount)
|
||||
i = i + readAmount
|
||||
assert(writeHandle:write(data))
|
||||
end
|
||||
assert(writeHandle:write(bytes))
|
||||
while true do
|
||||
local data = readHandle:read(chunkLength)
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
assert(writeHandle:write(data))
|
||||
end
|
||||
readHandle:close()
|
||||
writeHandle:close()
|
||||
fs.rename(tmpFilePath, filePath)
|
||||
end
|
||||
|
||||
local function remove(filePath, location, length)
|
||||
local chunkLength = 512
|
||||
if length > 512 then
|
||||
chunkLength = length
|
||||
end
|
||||
-- The file has to get shortened, so I have no choice but to do these shenanigans
|
||||
local readHandle = assert(fs.open(filePath, "r"))
|
||||
local tmpFilePath = filePath .. ".tmp"
|
||||
local writeHandle = assert(fs.open(tmpFilePath, "w"))
|
||||
local i = 0
|
||||
while true do
|
||||
local readAmount = chunkLength
|
||||
if readAmount > location - i then
|
||||
readAmount = location - i
|
||||
end
|
||||
if readAmount == 0 then
|
||||
break
|
||||
end
|
||||
local data = readHandle:read(readAmount)
|
||||
i = i + readAmount
|
||||
assert(writeHandle:write(data))
|
||||
end
|
||||
readHandle:seek(length)
|
||||
while true do
|
||||
local data = readHandle:read(chunkLength)
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
assert(writeHandle:write(data))
|
||||
end
|
||||
readHandle:close()
|
||||
writeHandle:close()
|
||||
fs.rename(tmpFilePath, filePath)
|
||||
end
|
||||
|
||||
local function adjustPatLocationsAfterPackage(filePath, packageName, offset)
|
||||
local readHandle, patLength = checkValidityAndOpen(filePath)
|
||||
local pat = readPat(readHandle, patLength)
|
||||
readHandle:close()
|
||||
local modifiedLocation = pat[packageName]
|
||||
local toAdjust = {}
|
||||
for name, location in pairs(pat) do
|
||||
if location > modifiedLocation then
|
||||
table.insert(toAdjust, { name = name, location = location })
|
||||
end
|
||||
end
|
||||
|
||||
if #toAdjust == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local readHandle = assert(fs.open(filePath, "r"))
|
||||
readHandle:seek(8) -- Skip header
|
||||
local patBytes = assert(readHandle:read(patLength))
|
||||
readHandle:close()
|
||||
|
||||
local patFieldPositions = {}
|
||||
for _, entry in ipairs(toAdjust) do
|
||||
local needle = entry.name .. "."
|
||||
local startPos = patBytes:find(needle, 1, true)
|
||||
patFieldPositions[entry.name] = 8 + startPos + #needle - 1
|
||||
end
|
||||
|
||||
local writeHandle = assert(fs.open(filePath, "a"))
|
||||
for _, entry in ipairs(toAdjust) do
|
||||
local newLocation = entry.location + offset
|
||||
writeHandle:seek("set", patFieldPositions[entry.name])
|
||||
writeHandle:write(string.pack("<I4", newLocation))
|
||||
end
|
||||
writeHandle:close()
|
||||
end
|
||||
|
||||
function solvitdb.create(path)
|
||||
checkArg(1, path, "string")
|
||||
local handle = assert(fs.open(path, "w"))
|
||||
assert(handle:write("\0\0\0\0RTFM"))
|
||||
handle:close()
|
||||
end
|
||||
|
||||
function solvitdb.set(path, name, data)
|
||||
checkArg(1, path, "string")
|
||||
checkArg(2, name, "string")
|
||||
checkArg(3, data, "table")
|
||||
|
||||
local function sanitize(tab)
|
||||
for _, item in pairs(tab) do
|
||||
if type(item) == "string" then
|
||||
item = item:lower():gsub("[;|]", "-")
|
||||
elseif type(item) == "table" then
|
||||
sanitize(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sanitize(data)
|
||||
|
||||
local readHandle, patLength = checkValidityAndOpen(path)
|
||||
local pat = readPat(readHandle, patLength)
|
||||
local writeHandle = assert(fs.open(path, "a"))
|
||||
|
||||
local encodedString = ""
|
||||
if data.type == "package" then
|
||||
encodedString = "P"
|
||||
elseif data.type == "group" then
|
||||
encodedString = "G"
|
||||
elseif data.type == "virtual-package" then
|
||||
encodedString = "V"
|
||||
end
|
||||
if data.dependencies then
|
||||
encodedString = encodedString .. "d" .. table.concat(data.dependencies, ";") .. "|"
|
||||
end
|
||||
if data.reverseDependencies then
|
||||
encodedString = encodedString .. "D" .. table.concat(data.reverseDependencies, ";") .. "|"
|
||||
end
|
||||
if data.conflicts then
|
||||
encodedString = encodedString .. "c" .. table.concat(data.conflicts, ";") .. "|"
|
||||
end
|
||||
if data.packages then
|
||||
encodedString = encodedString .. "p" .. table.concat(data.packages, ";") .. "|"
|
||||
end
|
||||
if data.version then
|
||||
encodedString = encodedString .. "v" .. data.version .. "|"
|
||||
end
|
||||
|
||||
if pat[name] then
|
||||
readHandle:seek(pat[name])
|
||||
local oldData, tmpdata = ""
|
||||
repeat
|
||||
tmpdata = readHandle:read(math.huge)
|
||||
oldData = oldData .. (tmpdata or "")
|
||||
until oldData:find("\n", 1, true) or not tmpdata
|
||||
readHandle:close()
|
||||
if not oldData:find("\n", 1, true) and not tmpdata then
|
||||
error("hit unexpected EOF")
|
||||
end
|
||||
oldData = oldData:match("^[^\n]+")
|
||||
local difference = #encodedString - #oldData
|
||||
writeHandle:seek("set", pat[name] + patLength + 8)
|
||||
if difference == 0 then
|
||||
readHandle:close()
|
||||
writeHandle:write(encodedString)
|
||||
writeHandle:close()
|
||||
elseif difference < 0 then
|
||||
writeHandle:write(encodedString)
|
||||
local currentSeek = writeHandle:seek()
|
||||
writeHandle:close()
|
||||
remove(path, currentSeek, -difference)
|
||||
adjustPatLocationsAfterPackage(path, name, difference)
|
||||
elseif difference > 0 then
|
||||
writeHandle:write(encodedString:sub(1, #encodedString - difference))
|
||||
local currentSeek = writeHandle:seek()
|
||||
writeHandle:close()
|
||||
insert(path, currentSeek + 1, encodedString:sub(#data - difference, -1))
|
||||
adjustPatLocationsAfterPackage(path, name, difference)
|
||||
end
|
||||
else
|
||||
readHandle:close()
|
||||
writeHandle:seek("end")
|
||||
local newPackageLocation = writeHandle:seek() - patLength - 8
|
||||
writeHandle:write(encodedString .. "\n")
|
||||
writeHandle:close()
|
||||
local patData = ("%s.%s;"):format(name, string.pack("<I4", newPackageLocation))
|
||||
insert(path, patLength + 8, patData)
|
||||
local newPatLength = patLength + #patData
|
||||
local writeHandle = assert(fs.open(path, "a")) -- If this works, that means handles must be reopened after insert() or remove()
|
||||
writeHandle:seek("set", 0)
|
||||
assert(writeHandle:write(string.pack("<I4", newPatLength)))
|
||||
writeHandle:close()
|
||||
end
|
||||
end
|
||||
|
||||
function solvitdb.get(path, name)
|
||||
checkArg(1, path, "string")
|
||||
checkArg(2, name, "string")
|
||||
local handle, patLength = checkValidityAndOpen(path)
|
||||
local pat = readPat(handle, patLength)
|
||||
if not pat[name] then
|
||||
return nil
|
||||
end
|
||||
handle:seek(pat[name])
|
||||
local data, tmpdata = ""
|
||||
repeat
|
||||
tmpdata = handle:read(math.huge)
|
||||
data = data .. (tmpdata or "")
|
||||
until data:find("\n", 1, true) or not tmpdata
|
||||
handle:close()
|
||||
if not data:find("\n", 1, true) and not tmpdata then
|
||||
error("hit unexpected EOF")
|
||||
end
|
||||
data = data:match("^[^\n]+")
|
||||
local output = {}
|
||||
if data:sub(1, 1) == "P" then
|
||||
output.type = "package"
|
||||
elseif data:sub(1, 1) == "G" then
|
||||
output.type = "group"
|
||||
elseif data:sub(1, 1) == "V" then
|
||||
output.type = "virtual-package"
|
||||
else
|
||||
error("unknown package type")
|
||||
end
|
||||
data = "." .. data:sub(2, -1)
|
||||
for series in data:gmatch("%.([dDcp][^.]*)") do
|
||||
local seriesOutput
|
||||
if series:sub(1, 1) == "d" then
|
||||
output.dependencies = {}
|
||||
seriesOutput = output.dependencies
|
||||
elseif series:sub(1, 1) == "D" then
|
||||
output.reverseDependencies = {}
|
||||
seriesOutput = output.reverseDependencies
|
||||
elseif series:sub(1, 1) == "c" then
|
||||
output.conflicts = {}
|
||||
seriesOutput = output.conflicts
|
||||
elseif series:sub(1, 1) == "p" then
|
||||
output.packages = {}
|
||||
seriesOutput = output.packages
|
||||
elseif series:sub(1, 1) == "v" then
|
||||
output.version = series:sub(2, -1)
|
||||
goto SkipSeries
|
||||
end
|
||||
-- Finally a case where Lua's weird table linking shenanigans are actually useful
|
||||
series = series:sub(2, -1)
|
||||
for seriesItem in series:gmatch("[^;]+") do
|
||||
table.insert(seriesOutput, seriesItem)
|
||||
end
|
||||
::SkipSeries::
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
function solvitdb.list(path)
|
||||
checkArg(1, path, "string")
|
||||
local handle, patLength = checkValidityAndOpen(path)
|
||||
local pat = readPat(handle, patLength)
|
||||
handle:close()
|
||||
local list = {}
|
||||
for index, _ in pairs(pat) do
|
||||
table.insert(list, index)
|
||||
end
|
||||
setmetatable(list, {
|
||||
__call = function(self)
|
||||
i, value = next(self, i)
|
||||
return i, value
|
||||
end,
|
||||
})
|
||||
return list
|
||||
end
|
||||
|
||||
function solvitdb.remove()
|
||||
|
||||
end
|
||||
|
||||
return solvitdb
|
||||
Reference in New Issue
Block a user