Files
Halyde/lib/solvit.lua
T

383 lines
10 KiB
Lua

-- local db = require("solvitdb")
local db = {}
local fs = require("filesystem")
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
local function packageInArray(pack,arr)
for i=1,#arr do
if arr[i][1]==pack[1] then -- TODO: check for compatible package version
return true
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 "/ag2/testdb.json"
if not fs.exists(dbpath) then
db.create(dbpath)
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)
packInfo[name]=info
-- print(require("serialize").table(packInfo))
end
local function getPackInfo(pack)
return packInfo[avs.serializePack(pack)]
end
local function finalizeInstall(settings)
installIncomplete=false
-- 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
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])
if (not packageInArray(dep,ins)) and type(db.get(dbpath,dep[1]))=="nil" then
installIncomplete=true
table.insert(ins,j,dep)
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
-- 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,deps[j])
packInfo[deps[j]]=depdat
end
if (not packageInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==avs.serializePack(rem[i])) 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
end
while removeIncomplete do
local out = {finalizeRemove(settings)}
if out[1]==false then return table.unpack(out) end
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: implement storing removal
-- TODO: implement storing reverse dependency removal
-- TODO: implement reverse dependency conflict when removing (don't conflict if the reverse dependencies are already in the list!)
-- TODO: handle same constant AVS
-- TODO: handle different constant AVS conflict
-- 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 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 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 packInfo[pack[1]] then
local info = table.copy(packInfo[pack[1]])
info.version=pack[2]
db.set(dbpath,pack[1],info)
end
end
-- set reverse dependencies
for _,pack in pairs(ins) do
local i = pack[1]
local v = packInfo[i]
if v and v.dependencies then
for _,dep in ipairs(v.dependencies) do
local dat = db.get(dbpath,dep)
if not dat then goto continue end
if type(dat.reverseDependencies)~="table" then
dat.reverseDependencies={}
end
table.insert(dat.reverseDependencies,i)
db.set(dbpath,dep,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 _,rdep in ipairs(rem) do
for _,pack in db.list(dbpath) do
local dat = db.get(dbpath,pack)
if dat.reverseDependencies then
removeFromArray(rdep[1],dat.reverseDependencies)
end
db.set(dbpath,pack,dat)
end
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 }