examples/ibb.lua
changeset 68 742878c74b8e
parent 66 542f61e113cb
--- a/examples/ibb.lua	Sat Mar 28 19:43:12 2009 +0200
+++ b/examples/ibb.lua	Tue Mar 31 18:35:34 2009 +0300
@@ -1,188 +1,138 @@
-
--- IN-BAND BYTESTREAMS (XEP-0047)
 
--- TODO bidirectionality
---        thus, on stream accept we can add our sid to incoming files structure,
---        as if we received and accepted incoming request.
---      message stanzas
+local lm  = require 'lm'
+local ibb = require 'lm.ibb'
 
--- library
+local mc_incoming_files = { }
 
-local lm     = require 'lm'
-local iq     = require 'iq'
-local base64 = require 'base64'
-
---
+ibb.handler (
+	function ( from, accept, reject )
+		local fid = #mc_incoming_files + 1
+		mc_incoming_files[fid] = {
+				from   = from,
+				accept =
+					function ( name )
+						mc_incoming_files[fid].name = name
+						accept (
+							function ( data )
+								local h = io.open ( mc_incoming_files[fid].name, 'w' )
+								if not h then
+									print ( 'Cannot open output file: ' .. mc_incoming_files[fid].name )
+									return
+								end
+								h:write ( data )
+								h:close ()
+								print ( 'Stream ' .. fid .. ' successfully saved to ' .. mc_incoming_files[fid].name )
+								mc_incoming_files[fid] = nil
+							end,
+							function ( mesg )
+								main.print_info ( from, 'Stream error: ' .. mesg )
+								mc_incoming_files[fid] = nil -- XXX
+							end )
+					end,
+				reject =
+					function ()
+						reject ()
+						print ( 'Stream ' .. fid .. ' rejected' )
+						mc_incoming_files[fid] = nil
+					end,
+		}
+		main.print_info ( from, from .. ' wants you to receive stream. Use /ibb [accept|reject] ' .. fid .. ' to process his request.' )
+	end )
 
-local F = { }
-local M = { }
-M.__index = M
-local O = {
-	handler =
-		function ( accept, reject )
-			reject ()
-		end,
-}
+local ibb_sid = 0
 
-function F.new ( conn, to, bs, sid )
-	local obj = {
-		conn = conn,
-		to   = to,
-		sbs  = bs,
-		bs   = math.floor ( bs * 3 / 4 ),
-		sid  = sid,
-		seq  = 0,
-	}
-	setmetatable ( obj, M )
-	return obj
-end
-
-function M.open ( obj, success, fail )
-	iq.send ( obj.conn, obj.to, 'set',
-		{
-			open = { sid = obj.sid, ['block-size'] = obj.sbs, xmlns = 'http://jabber.org/protocol/ibb' }
-		}, success, fail )
-end
-
-function M.send ( obj, data, success, fail )
-	if data and data ~= '' then
-		local start = 0
-		while start < data:len () do
-			local chunk = base64.encode ( data:sub ( start, obj.bs ) )
-			local cseq  = obj.seq -- local instance
-			iq.send ( obj.conn, obj.to, 'set',
-				{
-					data = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq,
-						chunk,
-					},
-				},
+main.command ( 'ibb',
+	function ( args )
+		local action = args[1]
+		if action == 'send' then
+			local who
+			if args.t then
+				who = args.t
+			else
+				who = main.full_jid ()
+			end
+			local fname = args[2]
+			local conn  = lm.connection.bless ( main.connection () )
+			local sid   = ibb_sid
+			ibb_sid     = ibb_sid + 1
+			local stream = ibb.new ( conn, who, 4096, sid )
+			stream:open (
 				function ()
-					success ( cseq )
+					main.print_info ( who, 'Stream accepted' )
+					local noerr = true
+					local h     = io.open ( fname, 'r' )
+					if not h then
+						print ( 'Cannot open file ' .. fname )
+						return
+					end
+					local data = h:read ( '*a' ) -- In fact, it is better to read it in chunks :/
+					h:close ()
+					local fail =
+						function ( mesg )
+							noerr = false
+							main.print_info ( who, 'Stream error: ' .. mesg )
+						end
+					stream:send ( data,
+						function ( seq )
+							main.print_info ( who, 'Delivery notification of chunk #' .. seq )
+						end, fail )
+					if noerr then
+						stream:close (
+							function ()
+								main.print_info ( who, 'Stream finalizing notification' )
+							end, fail )
+					end
+					if noerr then
+						main.print_info ( who, 'Stream sent' )
+					else
+						main.print_info ( who, 'Stream error occured' )
+					end
 				end,
 				function ( mesg )
-					noerr = false
-					fail ( mesg )
+					main.print_info ( who, 'Stream initiation error: ' .. mesg )
 				end )
-			start   = start + obj.bs
-			obj.seq = obj.seq + 1
+		elseif action == 'accept' then
+			local id = tonumber(args[2])
+			if mc_incoming_files[id] then
+				mc_incoming_files[id].accept ( args[3] )
+			end
+		elseif action == 'reject' then
+			local id = tonumber(args[2])
+			if mc_incoming_files[id] then
+				mc_incoming_files[id].reject ()
+			end
+		else
+			local text = ''
+			for sid, data in pairs ( mc_incoming_files ) do
+				text = text .. '\n' ..  sid .. ': ' .. data.from .. ' --> ' .. ( data.name or '?' )
+			end
+			if text ~= '' then
+				print ( 'List of incoming streams:' .. text )
+			else
+				print ( 'No streams' )
+			end
 		end
-	end
-end
+	end, true, { "send", "accept", "reject" } )
 
-function M.close ( obj, success, fail )
-	iq.send ( obj.conn, obj.to, 'set',
-		{
-			close = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb' },
-		}, success, fail )
-end
+
+commands_help['ibb'] = "[[-t target_jid] send filename | accept sid filename | reject sid]\n\nRequests, accepts or rejects sending file via in-band bytestream."
+
+local ibb_handler            = lm.message_handler.new ( ibb.iq_handler )
+local ibb_handler_registered = false
 
-function F.handler ( handler )
-	O.handler = handler
-end
-
-local ibb_files = {}
-
-function F.iq_handler ( conn, mess )
-	local mtype, smtype = mess:type ()
-	if smtype ~= 'set' then
-		return false
+hooks_d['hook-post-connect'].ibb =
+	function ( args )
+		lm.connection.bless( main.connection () ):handler ( ibb_handler, 'iq', 'normal' )
+		ibb_handler_registered = true
+		hooks_d['hook-post-connect'].ibb = nil
+		hooks_d['hook-quit'].ibb =
+			function ( args )
+				if ibb_handler_registered then
+					lm.connection.bless( main.connection () ):handler ( ibb_handler, 'iq' )
+				end
+			end
 	end
 
-	local child = mess:child ()
-	if not child or child:attribute ( 'xmlns' ) ~= 'http://jabber.org/protocol/ibb' then
-		return false
-	end
-
-	local id     = mess:attribute ( 'id' )
-	local from   = mess:attribute ( 'from' )
-	local action = child:name ()
-	local sid    = child:attribute ( 'sid' )
-
-	if action == 'open' then
-		if not ibb_files[sid] then
-			O.handler ( from,
-				function ( success, fail )
-					ibb_files[sid] = { from = from, success = success, fail = fail }
-					conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
-				end,
-				function ()
-					conn:send (
-						lm.message.create { to = from, mtype = 'iq-error', id = id,
-							error = { code = '405', type = 'cancel',
-								['not-allowed'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
-							},
-						} )
-				end )
-		else
-			conn:send (
-				lm.message.create { to = from, mtype = 'iq-error', id = id,
-					error = { code = '409', type = 'cancel',
-						conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
-					},
-				} )
-		end
-	elseif action == 'data' then
-		local seq = child:attribute ( 'seq' )
-		if ibb_files[sid] and from == ibb_files[sid].from and not ibb_files[sid][tonumber(seq)+1] then
-			local data = child:value ()
-			conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
-			ibb_files[sid][tonumber(seq)+1] = data
--- XXX		ibb_files[sid].success ( seq )
-		else
-			if ibb_files[sid] then
-				ibb_files[sid].fail ( 'conflict' )
-				ibb_files[sid] = nil -- invalidate session
-				conn:send (
-					lm.message.create { to = from, mtype = 'iq-error', id = id,
-						error = { code = '409', type = 'cancel',
-							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
-						},
-					} )
-			else
-				conn:send (
-					lm.message.create { to = from, mtype = 'iq-error', id = id,
-						error = { code = '404', type = 'cancel', -- XXX: check
-							['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
-						},
-					} )
-			end
-		end
-	elseif action == 'close' then
-		if ibb_files[sid] and from == ibb_files[sid].from then
-			local data = ''
-			for seq, chunk in ipairs ( ibb_files[sid] ) do
-				data = data .. chunk
-			end
-			local decoded = base64.decode ( data )
-			ibb_files[sid].success ( decoded )
-			conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
-			ibb_files[sid] = nil
-		else
-			if ibb_files[sid] then
-				ibb_files[sid].fail ( 'conflict' )
-				ibb_files[sid] = nil -- invalidate session
-				conn:send (
-					lm.message.create { to = from, mtype = 'iq-error', id = id,
-						error = { code = '409', type = 'cancel',
-							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
-						},
-					} )
-			else
-				conn:send (
-					lm.message.create { to = from, mtype = 'iq-error', id = id,
-						error = { code = '404', type = 'cancel', -- XXX: check
-							['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
-						},
-					} )
-			end
-		end
-	else
-		return false
-	end
-
-	return true
-end
-
-return F
+main.add_feature ( 'http://jabber.org/protocol/ibb' )
 
 -- vim: se ts=4: --