net/aft.lua
2017-06-08 09:34:22 -04:00

235 lines
8.8 KiB
Lua

require("net")
--General Stuff
--[[ What this module does!
Allows reliable transfer of files over the internet!
Hash testing and piece transfer
TODO: Add uploading support... For now use sft less intensive on the client/server
]]
net:registerModule("aft",{2,1,0})
net.aft.transfers={}
net.aft.sinks={}
net.aft.cache={}
net.aft.preload={}
net.aft.pieceSize=768 -- max data packet for b64 that can safely be transfered without erroring! DO NOT CHANGE!!!
function net.aft:init() -- calling this initilizes the library and binds it to the servers and clients created
--Server Stuff
net.OnServerCreated:connect(function(s)
print("The aft(Advance File Transfer) Module has been loaded onto the server!")
if s.Type~="tcp" then
print("It is recomended that you use tcp to transfer files!")
end
s.OnUploadRequest=multi:newConnection() -- create an aft event
s.OnDownloadRequest=multi:newConnection()
s.OnClientClosed:connect(function(self,reason,cid,ip,port)
if net.aft.transfers[cid] then
for i,v in pairs(net.aft.transfers[cid]) do
v.resendAlarm:Destroy()
end
net.aft.transfers[cid]=nil
end
end)
function s:preloadFile(name)
net.aft.preload[name]={}
local temp=bin.stream(name)
temp:segmentedRead(768,function(data)
local unpackedDATA1=data:sub(1,384)
local unpackedDATA2=data:sub(385)
local packedDATA=net.normalize(unpackedDATA1)..net.normalize(unpackedDATA2)
table.insert(net.aft.preload,packedDATA)
end)
end
function s:isPreloaded(name)
return net.aft.preload[name]~=nil
end
s.allowSmallFileCaching=false
s.cachable=10 -- 10 MBs
s.OnDataRecieved(function(self,data,cid,ip,port) -- when the server recieves data this method is triggered
local cmd,arg1,arg2=data:match("!aft! (%S+) (%S+) (%S+)")
--~ print(cmd,arg1,arg2)
if cmd=="PIECE" then
local FID,piecenum=arg1,tonumber(arg2)
local pp=piecenum-1
net.aft.transfers[cid][FID].resendAlarm:Reset()
if net.aft.transfers[cid][FID] then
if pp>net.aft.transfers[cid][FID].pieces-1 then
self:send(ip,"!aft! DOWNLOAD INVALID_PIECENUM NIL NIL NIL",port)
print("ERROR 101")
else
if self:isPreloaded(name) then
self:send(ip,net.aft.preload[name][piecenum],port)
return
end
if self.allowSmallFileCaching then
if net.aft.cache[net.aft.transfers[cid][FID].name] then
if net.aft.cache[net.aft.transfers[cid][FID].name][piecenum] then
self:send(ip,net.aft.cache[net.aft.transfers[cid][FID].name][piecenum],port)
return
end
end
end
local unpackedDATA=net.aft.transfers[cid][FID].sink:sub((pp*net.aft.pieceSize)+1,(pp+1)*net.aft.pieceSize)
local unpackedDATA1=unpackedDATA:sub(1,384)
local unpackedDATA2=unpackedDATA:sub(385)
local packedDATA=""
--~ if self.autoNormalization==false then -- if we already handled normalization in the main data packet then don't redo
packedDATA=net.normalize(unpackedDATA1)..net.normalize(unpackedDATA2)
--~ end
local hash=""--string.rep("0",64)--bin.new(unpackedDATA):getHash2(64)
net.aft.transfers[cid][FID].resendAlarm.piecenum=piecenum
net.aft.transfers[cid][FID].resendAlarm.hash=hash
net.aft.transfers[cid][FID].resendAlarm.packedDATA=packedDATA
local ddata="!aft! TRANS "..piecenum.." "..FID.." | "..packedDATA
if self.allowSmallFileCaching then
net.aft.cache[net.aft.transfers[cid][FID].name][piecenum]=ddata
end
self:send(ip,ddata,port)
end
else
self:send(ip,"!aft! DOWNLOAD INVALID_FID NIL NIL NIL",port)
print("ERROR 102")
end
elseif cmd=="REQUEST" then
local filename=arg1
local struct={
filename=filename
}
self.OnDownloadRequest:Fire(self,struct)
if io.fileExists(struct.filename) or struct.handle then
local FID=bin.new(filename):getRandomHash(16)
if struct.handle then
FID=struct.handle:getRandomHash(16)
end
if not net.aft.transfers[cid] then
net.aft.transfers[cid]={} -- setup server-client filestream
end
net.aft.transfers[cid][FID]={}
net.aft.transfers[cid][FID].name=struct.filename
if struct.handle then
net.aft.transfers[cid][FID].sink=struct.handle
else
net.aft.transfers[cid][FID].sink=bin.stream(struct.filename,false)
end
net.aft.transfers[cid][FID].size=net.aft.transfers[cid][FID].sink:getSize()
net.aft.transfers[cid][FID].pieces=math.ceil(net.aft.transfers[cid][FID].size/net.aft.pieceSize)
net.aft.transfers[cid][FID].resendAlarm=multi:newAlarm(.25)
net.aft.transfers[cid][FID].resendAlarm:OnRing(function(alarm)
if not(alarm.packedDATA) then return end
self:send(ip,"!aft! TRANS "..alarm.piecenum.." "..FID.." | "..alarm.packedDATA,port)
alarm:Reset()
end)
if self.allowSmallFileCaching then
if net.aft.transfers[cid][FID].size<=1024*self.cachable then -- 10 MB or smaller can be cached
net.aft.cache[struct.filename]={}
end
end
self:send(ip,"!aft! START "..net.aft.transfers[cid][FID].pieces.." "..FID.." "..filename.." NIL",port)
else
self:send(ip,"!aft! DOWNLOAD REQUEST_REFUSED NIL NIL NIL",port)
print("ERROR 103")
end
elseif cmd=="COMPLETE" then
net.aft.transfers[cid][arg1].resendAlarm:Destroy()
net.aft.transfers[cid][arg1]=nil
end
end,"aft") -- some new stuff
end)
--Client Stuff
net.OnClientCreated:connect(function(c)
c.OnPieceRecieved=multi:newConnection()
c.OnTransferStarted=multi:newConnection()
c.OnTransferCompleted=multi:newConnection()
c.OnFileRequestFailed=multi:newConnection() -- create an aft event
--c.OnFileUploadFailed=multi:newConnection() -- not yet must ensure oneway works well first
c.OnDataRecieved(function(self,data) -- when the client recieves data this method is triggered
local cmd,pieces,FID,arg1,arg2=data:match("!aft! (%S+) (%S+) (%S+) (%S+) (%S+)")
--~ print(cmd,pieces,FID,arg1,arg2)
if cmd=="START" then-- FID filename #pieces
local struct={
FID=FID,
filename=arg1,
numpieces=tonumber(pieces)
}
self.OnTransferStarted:Fire(self,struct)
local fid,filename,np=struct.FID,struct.filename,struct.numpieces
local sink=""
if type(net.aft.sinks[filename])=="table" then
sink=net.aft.sinks[filename]
sink.file=filename
else
if net.aft.sinks[filename] then
bin.new():tofile(net.aft.sinks[filename])
sink=bin.stream(net.aft.sinks[filename],false)
else
bin.new():tofile(filename)
sink=bin.stream(filename,false)
end
end
net.aft.transfers[FID]={}
net.aft.transfers[FID].name=sink.file
net.aft.transfers[FID].sink=sink
net.aft.transfers[FID].currentPiece=1
net.aft.transfers[FID].piecesRecieved=0
net.aft.transfers[FID].numpieces=tonumber(pieces)
c:requestPiece(FID,1)
elseif cmd=="TRANS" then-- self,data,FID,piecenum,hash
if self.autoNormalization==false then -- if we already handled normalization in the main data packet then don't redo
struct={
data=net.denormalize(arg2:sub(1,512))..net.denormalize(arg2:sub(513)),
FID=FID,
piecenum=tonumber(pieces),
numpieces=net.aft.transfers[FID].numpieces,
hash=arg1,
name=net.aft.transfers[FID].name,
}
else
struct={
data=arg2,
FID=FID,
piecenum=tonumber(pieces),
numpieces=net.aft.transfers[FID].numpieces,
hash=arg1,
name=net.aft.transfers[FID].name,
}
end
net.aft.transfers[FID].currentPiece=tonumber(pieces)
self.OnPieceRecieved:Fire(self,struct)
local data,FID,piecenum,hash=struct.data,struct.FID,struct.piecenum,struct.hash
if --[[bin.new(data):getHash2(64)==hash]] true then
net.aft.transfers[FID].sink:tackE(data)
net.aft.transfers[FID].piecesRecieved=net.aft.transfers[FID].piecesRecieved+1
if net.aft.transfers[FID].numpieces<=net.aft.transfers[FID].piecesRecieved then
print(net.aft.transfers[FID].name.." has finished downloading!")
net.aft.transfers[FID].sink:close()
self:send("!aft! COMPLETE "..FID.." NIL") -- for clean up purposes
self.OnTransferCompleted:Fire(self,net.aft.transfers[FID].name)
else
self:requestPiece(FID,piecenum+1) -- get next piece
end
else
print("Hash Bad! Requesting Again!")
self:requestPiece(FID,piecenum)
end
elseif cmd=="ERROR" then
local msg=FID
if pieces=="DOWNLOAD" then
print("Download Error!",msg)
else
print("UPLOAD Error!",msg)
end
end
end,"aft")
function c:requestFile(filename,sink) -- sinks data through a bin-stream sink if the filename you want otherwise the filename is used instead
self:send("!aft! REQUEST "..filename.." NIL")
if sink then
net.aft.sinks[filename]=sink
end
end
function c:requestPiece(FID,piecenum)
self:send("!aft! PIECE "..FID.." "..piecenum)
end
end)
end
if net.autoInit then
net.aft.init()
end