326 lines
13 KiB
Lua
326 lines
13 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 ddata
|
|
local unpackedDATA=net.aft.transfers[cid][FID].sink:sub((pp*net.aft.pieceSize)+1,(pp+1)*net.aft.pieceSize)
|
|
local num=#unpackedDATA
|
|
if num<384 then
|
|
ddata="!aft! TRANS "..piecenum.." "..FID.." | "..net.normalize(unpackedDATA)
|
|
else
|
|
local unpackedDATA1=unpackedDATA:sub(1,384)
|
|
local unpackedDATA2=unpackedDATA:sub(385)
|
|
local packedDATA=net.normalize(unpackedDATA1)..net.normalize(unpackedDATA2)
|
|
ddata="!aft! TRANS "..piecenum.." "..FID.." | "..packedDATA
|
|
end
|
|
net.aft.transfers[cid][FID].resendAlarm.piecenum=piecenum
|
|
net.aft.transfers[cid][FID].resendAlarm.hash="" -- not needed anymore
|
|
net.aft.transfers[cid][FID].resendAlarm.packedDATA=packedDATA
|
|
-- SAFE
|
|
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=="UPLOAD" then -- here we set up the spot for file writing
|
|
local FID,filename=arg1:match("(.-)|(.+)")
|
|
local struct={
|
|
FID=FID,
|
|
filename=filename,
|
|
numpieces=tonumber(arg2) or -1
|
|
}
|
|
if struct.numpieces==-1 then -- error handling catch it all :)
|
|
self:send(ip,"!aft! UPLOAD UPLOAD_REFUSED INVALID_NUMBER_OF_PIECES | |",port)
|
|
return
|
|
end
|
|
self.OnUploadRequest:Fire(struct,cid,ip,port)
|
|
if not(struct.deny) then -- block request or allow it
|
|
-- If we are allowed to lets do this
|
|
if not(net.aft.transfers.DOWNLOADS) then
|
|
net.aft.transfers.DOWNLOADS={}
|
|
end
|
|
if not(net.aft.transfers.DOWNLOADS[FID]) then
|
|
net.aft.transfers.DOWNLOADS[FID]={}
|
|
end
|
|
bin.new(""):tofile(struct.filename)
|
|
net.aft.transfers.DOWNLOADS[struct.FID].sink=struct.sink or bin.stream(struct.filename,false)
|
|
net.aft.transfers.DOWNLOADS[struct.FID].currentPiece=1
|
|
net.aft.transfers.DOWNLOADS[struct.FID].numPieces=tonumber(arg2)
|
|
--we got that setup... Lets Request a piece now!
|
|
self:send(ip,"!aft! PIECE 1 "..FID.." | |",port) -- request piece # 1
|
|
else
|
|
self:send(ip,"!aft! UPLOAD UPLOAD_REFUSED "..(struct.reason or "UNSPECIFIED_ERROR").." | |",port)
|
|
end
|
|
elseif cmd=="TRANS" then
|
|
local FID,piece=arg1:match("(.-)|(.+)")
|
|
local piece=tonumber(piece) or -1
|
|
if pieces==-1 then -- error handling catch it all :)
|
|
self:send(ip,"!aft! UPLOAD UPLOAD_CANCLED PIECE_DATA_MALFORMED | |",port)
|
|
return
|
|
end
|
|
if #arg2<512 then
|
|
net.aft.transfers.DOWNLOADS[FID].sink:tackE(net.denormalize(arg2))
|
|
else
|
|
net.aft.transfers.DOWNLOADS[FID].sink:tackE(net.denormalize(arg2:sub(1,512))..net.denormalize(arg2:sub(513)))
|
|
end
|
|
-- request the next piece
|
|
if piece==net.aft.transfers.DOWNLOADS[FID].numPieces then
|
|
-- We are done!
|
|
self:send(ip,"!aft! DONE "..FID.." | | |",port)
|
|
net.aft.transfers.DOWNLOADS[FID].sink:close() -- close the file
|
|
else
|
|
self:send(ip,"!aft! PIECE "..piece+1 .." "..FID.." | |",port)
|
|
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=="DONE" then
|
|
local FID=pieces
|
|
print(net.aft.transfers.UPLOADS[FID].name.." has Finished Uploading!")
|
|
self.OnTransferCompleted:Fire(self,net.aft.transfers[FID].name,"U")
|
|
net.aft.transfers[FID]=nil -- clean up
|
|
elseif cmd=="PIECE" then -- Server is asking for a piece to some file
|
|
local pieces=tonumber(pieces)
|
|
local pp=pieces-1
|
|
local unpackedDATA=net.aft.transfers.UPLOADS[FID].sink:sub((pp*net.aft.pieceSize)+1,(pp+1)*net.aft.pieceSize)
|
|
local num=#unpackedDATA
|
|
if num<384 then
|
|
self:send("!aft! TRANS "..FID.."|"..pieces.." "..net.normalize(unpackedDATA))
|
|
else
|
|
local unpackedDATA1=unpackedDATA:sub(1,384)
|
|
local unpackedDATA2=unpackedDATA:sub(385)
|
|
local packedDATA=net.normalize(unpackedDATA1)..net.normalize(unpackedDATA2)
|
|
self:send("!aft! TRANS "..FID.."|"..pieces.." "..packedDATA)
|
|
end
|
|
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
|
|
local ddata
|
|
if #arg2<512 then
|
|
ddata=net.denormalize(arg2)
|
|
else
|
|
ddata=net.denormalize(arg2:sub(1,512))..net.denormalize(arg2:sub(513))
|
|
end
|
|
struct={
|
|
data=ddata,
|
|
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
|
|
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
|
|
elseif cmd=="DOWNLOAD" then
|
|
local msg=FID
|
|
self.OnFileRequestFailed:Fire(msg)
|
|
print("Download Error!",msg)
|
|
elseif cmd=="UPLOAD" then
|
|
local msg=FID
|
|
self.OnFileUploadFailed:Fire(msg)
|
|
print("Upload Error!",msg)
|
|
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:requestUpload(filename)
|
|
if io.fileExists(filename) then
|
|
local FID=bin.new(filename):getRandomHash(16) -- We need this, but its not as important for client as it is for servers
|
|
local file=bin.stream(filename)
|
|
if not net.aft.transfers.UPLOADS then
|
|
net.aft.transfers.UPLOADS={}
|
|
end
|
|
if not net.aft.transfers.UPLOADS[FID] then
|
|
net.aft.transfers.UPLOADS[FID]={}
|
|
end
|
|
net.aft.transfers.UPLOADS[FID].sink=file -- client file management is much simpler since we only have to worry about 1 reciever/sender
|
|
net.aft.transfers.UPLOADS[FID].name=filename
|
|
net.aft.transfers.UPLOADS[FID].size=file:getSize()
|
|
net.aft.transfers.UPLOADS[FID].pieces=math.ceil(net.aft.transfers.UPLOADS[FID].size/net.aft.pieceSize)
|
|
self:send("!aft! UPLOAD "..FID.."|"..filename.." "..net.aft.transfers.UPLOADS[FID].pieces)-- Lets send the FID we will be using and the number of pieces the server should look out for
|
|
else
|
|
self.OnFileUploadFailed:Fire("File specified not found! "..filename.." does not exist!")
|
|
end
|
|
end
|
|
function c:requestPiece(FID,piecenum)
|
|
self:send("!aft! PIECE "..FID.." "..piecenum)
|
|
end
|
|
end)
|
|
end
|
|
if net.autoInit then
|
|
net.aft.init()
|
|
end
|