Skip to content

Instantly share code, notes, and snippets.

@JomerDev
Last active July 7, 2016 15:45
Show Gist options
  • Save JomerDev/9b94d0da7b912e2408fd3f6d9cc44464 to your computer and use it in GitHub Desktop.
Save JomerDev/9b94d0da7b912e2408fd3f6d9cc44464 to your computer and use it in GitHub Desktop.
File downloader
-- most of this is not needed fo the download, but this code keeps the app running even when there is an error
-- when an error happens, this code offers the option to update the files through the server
-- start at line 200 if you only want the file download service
function run()
if love.math then
love.math.setRandomSeed(os.time())
end
if love.load then love.load(arg) end
-- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end
local dt = 0
-- Main loop time.
while true do
-- Process events.
if love.event then
love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a
end
end
love.handlers[name](a,b,c,d,e,f)
end
end
-- Update dt, as we'll be passing it to update
if love.timer then
love.timer.step()
dt = love.timer.getDelta()
end
-- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
if love.graphics and love.graphics.isActive() then
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin()
if love.draw then love.draw() end
love.graphics.present()
end
if love.timer then love.timer.sleep(0.001) end
end
end
function love.errhand(msg)
msg = tostring(msg)
if msg:find("[QUIT]") then
return true
end
--local ip = "127.0.0.1" --"*"
local port = 6667 --6667
if not love.window or not love.graphics or not love.event then
return true
end
if not love.graphics.isCreated() or not love.window.isOpen() then
local success, status = pcall(love.window.setMode, 800, 600)
if not success or not status then
return true
end
end
-- Reset state.
if love.mouse then
love.mouse.setVisible(true)
love.mouse.setGrabbed(false)
love.mouse.setRelativeMode(false)
end
if love.keyboard then
love.keyboard.setTextInput(false)
end
if love.joystick then
-- Stop all joystick vibrations.
for _,v in ipairs(love.joystick.getJoysticks()) do
v:setVibration()
end
end
if love.audio then love.audio.stop() end
love.graphics.reset()
local font = love.graphics.setNewFont(30)
love.graphics.setBackgroundColor(89, 157, 220)
love.graphics.setColor(255, 255, 255, 255)
local trace = debug.traceback()
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin()
local err = {}
table.insert(err, "Error\n")
table.insert(err, msg.."\n\n")
for l in string.gmatch(trace, "(.-)\n") do
if not string.match(l, "boot.lua") then
l = string.gsub(l, "stack traceback:", "Traceback\n")
table.insert(err, l)
end
end
local p = table.concat(err, "\n")
p = string.gsub(p, "\t", "")
p = string.gsub(p, "%[string \"(.-)\"%]", "%1")
local w,h = love.graphics.getDimensions()
local turn = false
if w > h then
love.graphics.rotate(math.rad(-90))
love.graphics.translate(-h,0)
turn = true
w,h = h,w
end
local showUpdate = true
local update = false
local pos1_y = h-120
local pos2_y = h-240
local pos_x = 20
local pos_w = w-40
local pos_h = 100
local retVal = true
local function draw()
local pos = 70
love.graphics.setColor(255,255,255,255)
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.printf(tostring(p), pos, pos, love.graphics.getWidth() - pos)
love.graphics.setColor(200,0,0,255)
love.graphics.rectangle("fill",pos_x,pos1_y,pos_w,pos_h)
love.graphics.setColor(255,255,255,255)
local tw = font:getWidth("CLOSE")
local th = font:getHeight()
love.graphics.print("CLOSE", pos_x + (pos_w-tw)/2,pos1_y + (pos_h-th)/2)
if showUpdate then
love.graphics.setColor(192,192,192,255)
love.graphics.rectangle("fill",pos_x,pos2_y,pos_w,pos_h)
love.graphics.setColor(0,0,0,255)
local tw = font:getWidth("UPDATE")
local th = font:getHeight()
love.graphics.print("UPDATE", pos_x + (pos_w-tw)/2,pos2_y + (pos_h-th)/2)
end
love.graphics.present()
end
local updateState = 0
local service;
local net = require("net")
net.close()
while true do
love.event.pump()
for e, a, b, c, d in love.event.poll() do
if e == "quit" then
return true
elseif e == "keypressed" and a == "escape" then
if not update then
return false
end
elseif e == "touchpressed" then
local handled = update and true or false
if turn then
b,c=c,b
end
if b > pos_x and b < pos_x + pos_w then
if c > pos1_y and c < pos1_y + pos_h then
-- Close
return true
elseif c > pos2_y and c < pos2_y + pos_h and showUpdate then
-- update
update = true
service = net.getDiscoverService()
local a,b,c = service(port,"TCPING","TCPONG",10)
if a == "Finished" then
net.setIp(b,c)
updateState = 2
service = net.getTCPConnection()
end
updateState = 1
handled = true
showUpdate = false
end
end
if not handled then
local name = love.window.getTitle()
if #name == 0 or name == "Untitled" then name = "Game" end
local buttons = {"OK", "Cancel"}
local pressed = love.window.showMessageBox("Quit "..name.."?", "", buttons)
if pressed == 1 then
return false
end
end
elseif e == "mousepressed" and not d then
love.event.push("touchpressed",1,a,b)
end
end
draw()
if update then
if updateState == 1 then
local a,b,c = service(love.timer.getDelta())
p = "Discovering Server...\n Tries: "..tostring(b).."/"..tostring(c)
if a == "Finished" then
net.setIp(b,c)
updateState = 2
service = net.getTCPConnection()
elseif not a then
error(b)
end
elseif updateState == 2 then
local state,err = service()
if state then
updateState = 3
p = "Connected"
service = net.updateFiles()
elseif err then
error(err)
else
p = "Connecting to server..."
end
elseif updateState == 3 then
local state,a,b,c = pcall(service)
if state and a and b == "Finished" then
local function getAllFiles(path,data)
local data = data or {}
local files = love.filesystem.getDirectoryItems(path)
for _,v in pairs(files) do
if love.filesystem.isDirectory(path.."/"..tostring(v)) then
getAllFiles(path.."/"..tostring(v),data)
else
local str = (path.."/"..tostring(v)):gmatch(".+%.")()
str = str:gsub("/","."):sub(2,-2)
data[str] = true
end
end
return data
end
local state,files = pcall(getAllFiles,"/mobile")
for i in pairs(files) do
if package.loaded[i] then
package.loaded[i] = nil
end
end
retVal = false
break
end
p = table.concat({a,b,c},"\n")
end
end
if love.timer then
love.timer.sleep(0.1)
end
end
return retVal
end
function love.run()
while true do
local reload = false
if love.filesystem.isFile("mobile/init.lua") then
local state,err = pcall(require,"mobile.init")
if not state then
love.errhand(err)
end
else
love.errhand("No init file found. Please update...")
end
local state, br,err = xpcall(run,love.errhand)
if br == true then
break
end
end
end
io.stdout:setvbuf'no'
local socket = require("socket")
local udp = socket.udp()
udp:settimeout(0)
udp:setsockname("*",6667)
udp:setoption("broadcast",true)
local tcp = socket.tcp()
tcp:settimeout(0)
tcp:bind("*",6667)
tcp:listen()
tcp:setoption("reuseaddr",true)
local someRandomIP = "192.168.1.122" --This address you make up
local someRandomPort = "3102" --This port you make up
local mySocket = socket.udp() --Create a UDP socket like normal
--This is the weird part, we need to set the peer for some reason
mySocket:setpeername(someRandomIP,someRandomPort)
--I believe this binds the socket
--Then we can obtain the correct ip address and port
local myDevicesIpAddress, somePortChosenByTheOS = mySocket:getsockname()-- returns IP and Port
local clients = {}
local transfers = {}
local bytes = 4096
local function getAllFiles(path)
local files = love.filesystem.getDirectoryItems(path)
local data = {}
for i,v in pairs(files) do
if love.filesystem.isDirectory(path.."/"..tostring(v)) then
data[v] = getAllFiles(path.."/"..tostring(v))
else
local locPath = (path.."/"..tostring(v)):sub(8)
data[v] = {file = true,time = love.filesystem.getLastModified(path.."/"..tostring(v)),path = locPath}
end
end
return data
end
function updateClientData(client,id,bytes)
local client = client
client:settimeout(0)
local files = getAllFiles("/mobile")
local bytesPerMsg = bytes or 4096
local id = id
local size = 0
coroutine.yield(true)
local function loopFiles(tab)
for i,v in pairs(tab) do
if v.file then
client:send("TCFILE#"..tostring(v.path).."#"..tostring(v.time).."#\n")
local ans = ""
while true do
local msg = client:receive("*l")
if msg then
ans = msg
break
else
coroutine.yield(true)
end
end
if ans == "TCUPDATEFILE" then
local file = love.filesystem.newFile("/mobile"..v.path)
size = file:getSize()
client:send("TCSIZE#"..tostring(file:getSize()).."#"..tostring(bytesPerMsg).."#\n")
file:open("r")
client:settimeout(0.1)
while true do
local payload = file:read(bytesPerMsg)
file:seek(bytesPerMsg)
if payload then
size = size - bytesPerMsg
client:send(payload)
if size <= 0 then
break
end
coroutine.yield(true)
else
break
end
end
local ans = ""
while true do
local msg = client:receive("*l")
if msg then
ans = msg
break
else
coroutine.yield(true)
end
end
print(ans)
end
else
loopFiles(v)
end
end
end
loopFiles(files)
return true, id
end
function love.update(dt)
love.window.setTitle(tostring(love.timer.getFPS()))
local msg,addr,port = udp:receivefrom()
if msg then
print(msg,addr,port)
if msg == "TCPING" then
local s = "TCPONG"
udp:sendto(s,addr,port)
end
end
local client = tcp:accept()
if client then
print(client)
client:settimeout(0)
table.insert(clients,{socket = client, used = false})
end
for i,v in pairs(clients) do
if not v.used then
local msg = v.socket:receive("*l")
if msg then
print(msg)
if msg == "TCUPDATEFILES" then
local coro = coroutine.wrap(updateClientData)
v.used = true
coro(v.socket, i)
table.insert(transfers,coro)
elseif msg == "TCDONE" then
elseif msg == "TCQUIT" then
for j,k in pairs(users) do
if k.client == i then
clients[i] = nil
end
end
else
local data = {}
for dat in tostring(msg):gmatch(".-#") do
table.insert(data,dat:sub(1,-2))
end
if data[1] == "TCCONNECT" then
users[tostring(data[2])] = {
client = i
}
end
end
end
end
end
for i,v in pairs(transfers) do
local res,id = v()
if res and id then
clients[id].socket:send("TCRELOAD#\n")
transfers[i] = nil
clients[id].used = false
end
end
end
function love.draw()
love.graphics.setColor(255,255,255,255)
love.graphics.print(tostring(myDevicesIpAddress),10,10)
end
function love.quit()
udp:close()
tcp:close()
end
function love.quit()
udp:close()
tcp:close()
end
-- searches for server, gets servers ip and downloads all files
-- it tries to check which files to download, but that doesn't work right now
local net = {}
net.socket = require("socket")
function net.setIp(ip,port)
net.ip = ip
net.port = port
net.bytes = 4096
end
function net.close()
if net.tcp and net.tcp.close then
pcall(net.tcp.send,net.tcp,"TCQUIT")
pcall(net.tcp.close,net.tcp)
end
if net.udp and net.udp.close then
pcall(net.udp.close,net.udp)
end
net.connected = false
end
function net.isConnected()
return net.connected
end
function net.getTCP()
return net.tcp
end
local function discoverServer(port,ping,pong,maxtries)
net.udp = net.socket.udp()
net.udp:settimeout(0)
net.udp:setoption("broadcast",true)
local timer = 5
local port = port
local ping = ping or "PING"
local pong = pong or "PONG"
local dt = 0
local tries = 0
local maxtries = maxtries or 10
while true do
timer = timer + dt
if timer >= 5 then
timer = timer - 5
tries = tries + 1
if tries >= maxtries then
net.udp:close()
return false,"Server not found"
end
local state, err = net.udp:sendto(ping,"255.255.255.255",port)
if not state then
return false, err
else
dt = coroutine.yield("working",tries,maxtries)
end
end
local msg, addr, por = net.udp:receivefrom()
if msg == pong then
net.udp:close()
return "Finished",addr,por
else
dt = coroutine.yield("working",tries,maxtries)
end
end
end
function net.getDiscoverService()
return coroutine.wrap(discoverServer)
end
function getTCP()
net.tcp = net.socket.tcp()
net.tcp:settimeout(1)
local state,err = net.tcp:connect(net.ip, net.port)
if not state then
return false, err
else
net.connected = true
return true
end
while true do
local data = socket.select({net.tcp}, {net.tcp} ,0.1)
if #data > 0 then
net.connected = true
return true
else
coroutine.yield(false)
end
end
end
function net.getTCPConnection()
return coroutine.wrap(getTCP)
end
function updateFiles(...)
net.tcp:settimeout(0.1)
local function getAllFiles(path)
local files = love.filesystem.getDirectoryItems(path)
local data = {}
for _,v in pairs(files) do
if love.filesystem.isDirectory(path.."/"..tostring(v)) then
data[v] = getAllFiles(path.."/"..tostring(v))
else
data[v] = "FILE"
end
end
return data
end
local files = getAllFiles("/mobile")
net.tcp:send("TCUPDATEFILES\n")
local filebytes = 0
local bytesPerMsg = 4096
local receivedBytes = 0
local fileTransfer = false
local filePath = ""
local file;
while true do
if not fileTransfer then
local msg = net.tcp:receive("*l")
if msg then
local data = {}
for dat in tostring(msg):gmatch(".-#") do
table.insert(data,dat:sub(1,-2))
end
if data[1] == "TCFILE" then
local file = files
for path in data[2]:gmatch("/.-") do
path = path:sub(2)
if file[path] then
if file[path] == "FILE" then
local modtime, errormsg = love.filesystem.getLastModified(tostring(data[2]) )
if errormsg then
net.tcp:send("TCUPDATEFILE\n")
filePath = tostring(data[2])
break
else
if tostring(modtime) == data[3] then
net.tcp:send("TCNEXTFILE\n")
break
else
net.tcp:send("TCUPDATEFILE\n")
filePath = tostring(data[2])
break
end
end
else
file = file[path]
end
else
net.tcp:send("TCUPDATEFILE\n")
filePath = tostring(data[2])
break
end
end
elseif data[1] == "TCSIZE" then
filebytes = tonumber(data[2])
bytesPerMsg = data[3] or bytesPerMsg
fileTransfer = true
if love.filesystem.exists("/mobile"..filePath) then
love.filesystem.remove("/mobile"..filePath)
end
local dirPath = filePath:gmatch(".+/")()
if dirPath then
love.filesystem.createDirectory("/mobile"..tostring(dirPath))
end
file = love.filesystem.newFile("/mobile"..filePath)
file:open("w")
receivedBytes = 0
elseif data[1] == "TCRELOAD" then
return true, "Finished"
end
end
else
if receivedBytes + bytesPerMsg > filebytes then
bytesPerMsg = filebytes-receivedBytes
end
local payload, err, partial = net.tcp:receive(bytesPerMsg)
if payload then
receivedBytes = receivedBytes + bytesPerMsg
file:write(payload)
if receivedBytes >= filebytes then
file:close()
net.tcp:send("TCDONE\n")
fileTransfer = false
end
end
end
coroutine.yield("working",fileTransfer and "Receiving file...\n"..tostring(filePath).."\n"..tostring(receivedBytes).." / "..tostring(filebytes).."bytes" or "Updating files\n"..tostring(filePath))
end
end
function net.updateFiles()
return coroutine.wrap(updateFiles)
end
return net
@JomerDev
Copy link
Author

JomerDev commented Jul 6, 2016

How to use:
Put the main.lua for mobile and net.lua on your (android) phone and run it. It should display an error that it can't find any init.lua file.

Now start the servers main.lua on any device in the same network. create a folder mobile next to it and put any files for the mobile into it.
That should include an init.lua file, in which your main code (love.load, love.draw, etc) is.

when the server has started, just press the update button on the mobile phone and it 'should' try to find the server by broadcasting a ping.
As soon as the server is found it will copy all files in the mobile folder onto your phone.

When it is done it will try to restart, but that doesn't work yet.

! You need to restart love2d after updating this way ! The new files only show up after a restart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment