ComputerCraft Archive

disknet

computer networking LDDestroier github

Description

A collection of all my ComputerCraft programs and the APIs they use. This is mostly just to get them the fuck off of pastebin, and also to ensure that API owners don't change things to break my precious programs...!

Installation

Copy one of these commands into your ComputerCraft terminal:

wget:wget https://raw.githubusercontent.com/LDDestroier/CC/master/disknet.lua disknet
Archive:wget https://cc.shobie.xyz/cc/get/gh-LDDestroier-CC-disknet disknet
Quick Install: wget https://cc.shobie.xyz/cc/get/gh-LDDestroier-CC-disknet disknet

Usage

Run: disknet

Tags

networking

Source

View Original Source

Code Preview

--[[
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