--[[ DISKNET - by LDDestroier Refer to https://github.com/LDDestroier/CC/blob/master/disknet.md for documentation --]] local disknet = disknet or {} local tArg = {...} disknet.mainPath = "disk/DISKNET" -- path of shared file local limitChannelsToModem = false -- if true, can only use number channels from 1 to 65535 local maximumBufferSize = 64 -- largest amount of messages per channel buffered disknet.checkDelay = 0.2 -- amount of time (seconds) between checking the file -- if 0, checks super fast so don't do that local isUsingTweaked = false if _HOST then if _HOST:find("CCEmuX") or _HOST:find("CC:Tweaked") or _HOST:find("[(]Minecraft") then isUsingTweaked = true end end local openChannels = {} local yourID = os.getComputerID() local uniqueID = math.random(1, 2^31 - 1) -- prevents receiving your own messages disknet.msgCheckList = {} -- makes sure duplicate messages aren't received local ageToToss = 0.005 -- amount of time before a message is removed -- used for synching times between different emulators disknet._timeMod = 0 -- do not think for one second that os.epoch("utc") would be a proper substitute local getTime = function() if os.day then return (os.time() + (-1 + os.day()) * 24) + disknet._timeMod else return os.time() + disknet._timeMod end end local function serialize(tbl) local output = "{" local noKlist = {} local cc = table.concat for i = 1, #tbl do if type(tbl[i]) == "table" then output = output .. serialize(tbl[i]) elseif type(tbl[i]) == "string" then output = cc({output, "\"", tbl[i], "\""}) else output = output .. tbl[i] end noKlist[i] = true output = output .. "," end for k,v in pairs(tbl) do if not noKlist[k] then if type(k) == "number" or type(k) == "table" then output = cc({output, "[", k, "]="}) else output = cc({output, k, "="}) end if type(v) == "table" then output = output .. serialize(v) elseif type(v) == "string" then output = cc({output, "\"", v, "\""}) else output = output .. v end output = output .. "," end end return output:sub(1, -2):gsub("\n", "\\n") .. "}" end local readFile = function(path) local file = fs.open(path, "r") local contents = file.readAll() file.close() return contents end local writeFile = function(path, contents) local file = fs.open(path, "w") file.write(contents) file.close() end -- if 'limitChannelsToModem', then will make sure that channel is a number between 0 and 65535 local checkValidChannel = function(channel) if limitChannelsToModem then if type(channel) == "number" then if channel < 0 or channel > 65535 then return false, "channel must be between 0 and 65535" else return true end else return false, "channel must be number" end else if type(channel) == "string" or type(channel) == "number" then return true else return false, "channel must be castable to string" end end end disknet.isOpen = function(channel) local valid, grr = checkValidChannel(channel) if valid then for i = 1, #openChannels do if openChannels[i] == channel then return true end end return false else error(grr) end end isOpen = disknet.isOpen disknet.open = function(channel) local valid, grr = checkValidChannel(channel) if valid then openChannels[#openChannels + 1] = channel return true else error(grr) end end open = disknet.open disknet.close = function(channel) local valid, grr = checkValidChannel(channel) if valid then for i = 1, #openChannels do if openChannels[i] == channel then table.remove(openChannels, i) return true end end return false else error(grr) end end close = disknet.close disknet.closeAll = function() openChannels = {} end closeAll = disknet.closeAll disknet.send = function(channel, message, recipient) local valid, grr = checkValidChannel(channel) if valid then if not fs.exists(fs.combine(disknet.mainPath, tostring(channel))) then fs.makeDir(disknet.mainPath) fs.open(fs.combine(disknet.mainPath, tostring(channel)), "w").close() end local contents = textutils.unserialize(readFile(fs.combine(disknet.mainPath, tostring(channel)))) local cTime = getTime() if disknet.isOpen(channel) then local file = fs.open(fs.combine(disknet.mainPath, tostring(channel)), "w") if contents then contents[#contents + 1] = { time = cTime, id = yourID, uniqueID = uniqueID, messageID = math.random(1, 2^31 - 1), channel = channel, recipient = recipient, message = message, } for i = #contents, 1, -1 do if cTime - (contents[i].time or 0) > ageToToss then table.remove(contents, i) end end if #contents > maximumBufferSize then table.remove(contents, 1) end file.write(serialize(contents)) else file.write(serialize({{ time = cTime, id = yourID, uniqueID = uniqueID, messageID = math.random(1, 2^31 - 1), channel = channel, message = message, }}):gsub("\n[ ]*", "")) end file.close() return true else return false end else error(grr) end end send = disknet.send local fList, pList, sList = {}, {}, {} local loadFList = function() fList, pList = {}, {} if channel then fList = {fs.open(fs.combine(disknet.mainPath, tostring(channel)), "r")} pList = {fs.combine(disknet.mainPath, tostring(channel))} else for i = 1, #openChannels do fList[i] = fs.open(fs.combine(disknet.mainPath, tostring(openChannels[i])), "r") pList[i] = fs.combine(disknet.mainPath, tostring(openChannels[i])) end end end -- returns: string message, string/number channel, number senderID, number timeThatMessageWasSentAt disknet.receive = function(channel, senderFilter) local valid, grr = checkValidChannel(channel) if valid or not channel then local output, contents local doRewrite = false local good, goddamnit = pcall(function() local cTime = getTime() local goWithIt = false while true do loadFList() for i = 1, #fList do contents = fList[i].readAll() if contents ~= "" then contents = textutils.unserialize(contents) if type(contents) == "table" then if contents[1] then if not output then for look = 1, #contents do if (contents[look].uniqueID ~= uniqueID) and (not disknet.msgCheckList[contents[look].messageID]) then -- make sure you're not receiving messages that you sent if (not contents[look].recipient) or contents[look].recipient == yourID then -- make sure that messages intended for others aren't picked up if (not channel) or channel == contents[look].channel then -- make sure that messages are the same channel as the filter, if any if (not senderFilter) or senderFilter == contents[look].id then -- make sure that the sender is the same as the id filter, if any if (not isUsingTweaked) and math.abs(contents[look].time - getTime()) >= ageToToss then -- if using something besides CC:Tweaked/CCEmuX, adjust your time. disknet._timeMod = contents[look].time - getTime() cTime = getTime() goWithIt = true end if cTime - (contents[look].time or 0) <= ageToToss or goWithIt then -- make sure the message isn't too old disknet.msgCheckList[contents[look].messageID] = true output = {} for k,v in pairs(contents[look]) do output[k] = v end break end end end end end end end -- delete old msesages doRewrite = false for t = #contents, 1, -1 do if cTime - (contents[t].time or 0) > ageToToss or cTime - (contents[t].time or 0) < -1 then disknet.msgCheckList[contents[t].messageID] = nil table.remove(contents, t) doRewrite = true end end if doRewrite then writeFile(pList[i], serialize(contents)) end if output then break end end end end end if output then break else if disknet.checkDelay > 0 then sleep(disknet.checkDelay) else os.queueEvent("") os.pullEvent("") end end for i = 1, #fList do fList[i].close() end fList, pList = {}, {} end end) if good then if contents then return output.message, output.channel, output.id, output.time + disknet._timeMod else return nil end else for i = 1, #fList do fList[i].close() end error(goddamnit, 0) end else error(grr) end end receive = disknet.receive -- not really needed if going between CCEmuX and another emulator, but may be needed between two separate CCEmuX daemons disknet.receive_TS = function(...) local message, channel, id, time = disknet.receive(...) if time then disknet._timeMod = time - getTime() end return message, channel, id, time end receive_TS = disknet.receive_TS return disknet