--- /dev/null
+local args = { ... }
+local PROTOCOL = "kv"
+local BSPROTOCOL = "kv-bs"
+local NAMESPACE = "" -- global prefix for protocol, ex: "system2."
+local KEYPREFIX = "" -- local prefix for put/get, ex: "za3k."
+if KEYPREFIX == "" and fs.exists(".kv-prefix") then
+ f = fs.open(".kv-prefix", "r")
+ KEYPREFIX = f.readAll()
+ f.close()
+end
+local SERVER = NAMESPACE .. "kv-server"
+local KVKEY = NAMESPACE .. "kv"
+local REGFILE = ".register"
+local RUNFILE = ".kv-run"
+local shouldRegister = true
+local findModem = function()
+ for _,side in ipairs(rs.getSides()) do
+ if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
+ return side
+ end
+ end
+ print("no modem available")
+end
+
+local printUsage = function()
+ print("kv update")
+ print(" Update the 'kv' program to the lastest version")
+ print("kv get <keyname> [<filename>]")
+ print(" Get a program over wifi")
+ print("kv put [<filename>] <keyname>")
+ print(" Store a program over wifi")
+ print("kv run <keyname> [...]")
+ print(" Immediately run the named program")
+ print("kv host [FILE]")
+ print(" Host a database server.")
+ print("kv host-dump FILE [PREFIX]")
+ print(" (as host) Prints all kv-pairs")
+ print("Edit .kv-prefix to set a global prefix")
+end
+local host = function(dbFile, quineDisable, noImmediate)
+ db = loadDB(dbFile)
+ if db == nil then
+ db = {}
+ else
+ print("loaded "..dbFile)
+ end
+ if not quineDisable then db[KVKEY] = readFile(KVKEY) end
+ local modemSide = findModem()
+ rednet.open(modemSide)
+ rednet.host(PROTOCOL, SERVER)
+ print("Hosting kv database")
+ print("To bootstrap run:")
+ print(" rednet.open(\"back\")")
+ print(" _,b,_=rednet.receive(\""..BSPROTOCOL.."\")")
+ print(" f=fs.open(\""..KVKEY.."\",\"w\")")
+ print(" f.write(b)")
+ print(" f.close()")
+ local dirty = false
+ while true do
+ clientID, message, protocol = rednet.receive(PROTOCOL, 60)
+ if clientID == nil then
+ -- Broadcast a bootstrap periodically
+ if not quineDisable then
+ --print("broadcast bs")
+ local bs = db[KVKEY]
+ rednet.broadcast(bs, BSPROTOCOL)
+ end
+ if dbFile and dirty then
+ print("write "..dbFile)
+ saveDB(dbFile, db)
+ dirty = false
+ end
+ elseif message.action == "get" then
+ print("get "..message.key)
+ local value = db[message.key]
+ response = {
+ ["action"]="getResponse",
+ ["key"]=message.key,
+ ["value"]=value
+ }
+ rednet.send(clientID, response, PROTOCOL)
+ elseif message.action == "put" then
+ print("put "..message.key)
+ db[message.key] = message.value
+ if noImmediate then
+ dirty = true
+ else
+ print("write "..dbFile)
+ saveDB(dbFile, db)
+ dirty = false
+ end
+ end
+ end
+ rednet.unhost(PROTOCOL, SERVER)
+ rednet.close(modemSide)
+end
+local get = function(k, prefix)
+ local modemSide = findModem()
+ prefix = prefix or KEYPREFIX
+ local key = NAMESPACE .. prefix .. k
+ rednet.open(modemSide)
+ local serverID = rednet.lookup(PROTOCOL, SERVER)
+ local message = {
+ ["action"] = "get",
+ ["key"] = key,
+ }
+ rednet.send(serverID, message, PROTOCOL)
+ local value = nil
+ while true do
+ peerID, message, protocol = rednet.receive(PROTOCOL, 10)
+ if peerID == nil then
+ break
+ elseif peerID == serverID and message.action == "getResponse" and message.key == key then
+ value = message.value
+ break
+ end
+ end
+ rednet.close()
+ return value
+end
+local put = function(k, v, prefix)
+ prefix = prefix or KEYPREFIX
+ local key = NAMESPACE .. prefix .. k
+ local modemSide = findModem()
+ rednet.open(modemSide)
+ local serverID = rednet.lookup(PROTOCOL, SERVER)
+ message = {
+ ["action"] = "put",
+ ["key"] = key,
+ ["value"] = v
+ }
+ rednet.send(serverID, message, PROTOCOL)
+ rednet.close()
+end
+writeFile = function(path, content)
+ local f = fs.open(path, "w")
+ f.write(content)
+ f.close()
+end
+readFile = function(path)
+ local f = fs.open(path, "r")
+ content = f.readAll()
+ f.close()
+ return content
+end
+loadDB = function(path)
+ if path == nil or not fs.exists(path) then
+ return nil
+ else
+ return textutils.unserialise(readFile(path))
+ end
+end
+saveDB = function(path, db)
+ writeFile(path, textutils.serialise(db))
+end
+local run = function(content, ...)
+ writeFile(RUNFILE, content)
+ shell.run(RUNFILE, ...)
+end
+local autoregister = function()
+ if shouldRegister and not fs.exists(REGFILE) then
+ writeFile(REGFILE, "registered: true")
+ local id = os.getComputerID()
+ local label = os.getComputerLabel()
+ put("registration."..id..".label", label)
+ put("registration."..id..".namespace", NAMESPACE)
+ end
+end
+
+if #args < 1 then
+ printUsage(); return
+elseif args[1] == "host" then
+ if #args == 1 then dbpath = nil
+ elseif #args == 2 then dbpath = args[2]
+ else printUsage(); return
+ end
+ host(dbpath)
+elseif args[1] == "get" then
+ autoregister()
+ if #args == 2 then from, to = args[2], args[2]
+ elseif #args == 3 then from, to = args[2], args[3]
+ else printUsage(); return
+ end
+ content = get(from)
+ if content == nil then print("content not found"); return end
+ writeFile(to, content)
+elseif args[1] == "put" then
+ autoregister()
+ if #args == 2 then from, to = args[2], args[2]
+ elseif #args == 3 then from, to = args[2], args[3]
+ else printUsage(); return
+ end
+ if not fs.exists(from) then print("file does not exist"); return end
+ content = readFile(from)
+ put(to, content)
+elseif args[1] == "run" then
+ autoregister()
+ if #args >= 2 then
+ local from = args[2]
+ table.remove(args, 1)
+ table.remove(args, 1)
+ local runArgs = args
+ else printUsage(); return
+ end
+ content = get(from)
+ if content == nil then print("content not found"); return end
+ run(content, unpack(runArgs))
+elseif args[1] == "update" then
+ autoregister()
+ if #args == 1 then
+ old = readFile(KVKEY)
+ content = get(KVKEY, "")
+ if old == content then
+ print("Already up to date")
+ else
+ print("updating")
+ writeFile(KVKEY, content)
+ end
+ else
+ printUsage(); return
+ end
+elseif args[1] == "host-dump" then
+ if #args == 2 then path, prefix = args[2], nil
+ elseif #args == 3 then path, prefix = args[2], args[3]
+ else printUsage(); return
+ end
+ db = loadDB(path)
+ if not db then print("DB file not found"); return end
+ for k,v in pairs(db) do
+ if not prefix or (k and string.find(k, prefix, 1, true)==1) then
+ print(k, v)
+ end
+ end
+else printUsage(); return
+end