Este é meu segundo post sobre como usar Ruby com o protocolo XMPP. No primeiro descrevi como enviar e receber mensagens com Ruby e XMPP, agora irei mostrar como enviar arquivos de um cliente Jabber para um outro.
Antes de começar gostaria de informar que vamos utilizar os mesmos recursos de servidor Jabber e usuários já configurados no meu primeiro artigo desta série. A novidade aqui fica por conta do código. Iremos começar pelo cliente que ficará encarregado de receber os arquivos.
O script receiver.rb vai instanciar a classe ReceiveFile do arquivo receive_file.rb que faz toda a reponsabilidade do recebimento.
receiver.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/usr/bin/env ruby require 'rubygems' require 'xmpp4r' require 'receive_file' client = Jabber::Client.new(Jabber::JID.new('neo@localhost/Home')) client.connect('localhost', '5222') client.auth('123456') client.send(Jabber::Presence.new.set_type(:available)) client.send(Jabber::Presence.new(:chat, 'Files Transfer!')) receive_file = ReceiveFile.new receive_file.wating_for_incoming(client, '/tmp') |
receive_file.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | require 'xmpp4r/bytestreams' class ReceiveFile def wating_for_incoming(client = nil, destination_dir = nil) @file_transfer = Jabber::FileTransfer::Helper.new(client) receive_file_callback(destination_dir) puts "Waiting for incoming files..." Thread.stop end private def receive_file_callback(destination_dir = nil) @file_transfer.add_incoming_callback do |iq, file| puts "Incoming file transfer from #{iq.from}: #{file.fname} (#{format_file_size(file.size)})" filename = "#{destination_dir}/#{file.fname.split(/\//).last}" file_offset = check_incoming_file_offset(iq, file, filename) Thread.new { begin puts "Accepting #{file.fname}" @stream = @file_transfer.accept(iq, file_offset) if @stream.kind_of?(Jabber::Bytestreams::SOCKS5Bytestreams) @stream.connect_timeout = 60 add_stream_callback end handle_receiving(filename, file_offset) rescue Exception => exception puts "#{exception.class}: #{exception}\n#{exception.backtrace.join("\n")}" end } # Thread end end def check_incoming_file_offset(iq = nil, file = nil, filename = nil) file_offset = nil if File::exist?(filename) puts "#{filename} already exists" if (File::size(filename) < file.size) && (file.range) file_offset = File::size(filename) puts "Peer supports <range/>, will retrieve #{file.fname} starting at #{file_offset}" else puts "#{file.fname} is already fully retrieved, declining file-transfer" @file_transfer.decline(iq) end end return file_offset end def add_stream_callback @stream.add_streamhost_callback do |streamhost, state, exception| case state when :connecting puts "Connecting to #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" when :success puts "Successfully using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" when :failure puts "Error using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port}): #{exception}" end end end def handle_receiving(filename = nil, file_offset = nil) puts "Waiting for stream configuration..." if @stream.accept puts "Stream established" outfile = File.new(filename, (file_offset ? 'a' : 'w')) while buf = @stream.read break if buf.nil? || buf == '' outfile.write(buf) print '.' $stdout.flush end puts '!' outfile.close @stream.close else raise 'Stream failed' end end def format_file_size(bytes_size = 0) kilo_bytes_size = bytes_size / 1024 return "#{(kilo_bytes_size.to_i == 0) ? bytes_size.to_s + ' B' : kilo_bytes_size.to_s + ' KB' }" end end |
Agora execute o receiver.rb:
Waiting for incoming files. . .
Neste momento ele está esperando que alguém envie um arquivo. Vamos agora aprender como enviar um arquivo, de acordo com o código que se segue.
Aqui utilizei o mesmo principio do receiver: o sender.rb instancia a classe SendFile do arquivo send_file.rb.
sender.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/usr/bin/env ruby require 'rubygems' require 'xmpp4r' require 'send_file' client = Jabber::Client.new(Jabber::JID.new('acc@localhost/Home')) client.connect('localhost', '5222') client.auth('123456') client.send(Jabber::Presence.new.set_type(:available)) client.send(Jabber::Presence.new(:chat, 'Files Transfer!')) send_file = SendFile.new status = send_file.send(client, 'neo@localhost/Home', '/home/acc/document.txt') |
send_file.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | require 'xmpp4r/bytestreams' class SendFile def send(from = nil, to = nil, filename = nil) begin file_transfer = Jabber::FileTransfer::Helper.new(from) @source = Jabber::FileTransfer::FileSource.new(filename) puts "Offering #{@source.filename} to #{to}" @stream = file_transfer.offer(Jabber::JID.new(to), @source) return handler_stremam rescue Exception => exception raise exception.to_s end end private def handler_stremam if @stream stream_callback @stream.open if @stream.kind_of? Jabber::Bytestreams::SOCKS5BytestreamsInitiator puts "Using streamhost #{@stream.streamhost_used.jid} (#{@stream.streamhost_used.host}:#{@stream.streamhost_used.port})" end puts 'Stream established' while buf = @source.read print '.' $stdout.flush @stream.write buf @stream.flush end puts '!' @stream.close return true else puts 'Error while hadling stream!' return false end end def stream_callback if @stream.kind_of? Jabber::Bytestreams::SOCKS5BytestreamsInitiator @stream.add_streamhost(socket_server) # TODO: Proxy configuration ([]).each { |proxy| @stream.add_streamhost proxy } @stream.add_streamhost_callback { |streamhost, state, exception| case state when :connecting puts "Connecting to #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" when :success puts "Successfully using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" when :failure puts "Error using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port}): #{exception}" end } end end def socket_server # TODO: Socket configuration puts 'Biding Local server: locahost:#65010' begin bss = Jabber::Bytestreams::SOCKS5BytestreamsServer.new('65010') bss.add_address('localhost') return bss rescue Exception => exception raise exception.to_s end end end |
Agora basta executar o sender.rb que a transferência de arquivos começará, de acordo com o output abaixo:
Offering document.txt to neo@localhost/Home
Biding Local server: locahost:#65010
Successfully using acc@localhost/Home (localhost:65010)
Using streamhost acc@localhost/Home (localhost:65010)
Stream established
……………….!
Voltando ao shell do receiver.rb teremos o seguinte output:
Waiting for incoming files…
Incoming file transfer from acc@localhost/Home: document.txt (53 kB)
Accepting document.txt
Waiting for stream configuration…
Connecting to acc@localhost/Home (127.0.0.1:65010)
Successfully using acc@localhost/Home (127.0.0.1:65010)
Stream established
.!
Pronto! Veja agora que existe um arquivo transferido da sua pasta de origem até a sua pasta de destino.
Xmpp4r no Windows
O Xmpp4r apresenta problema ao transferir arquivos binários a partir de máquinas Windows. Para resolver este problema abra para edição o arquivo:
Procure no código o método initialize da classe FileSource e mude a seguinte linha de:
1 | @file = File.new(filename) |
para:
1 | @file = File.open(filename, 'rb') |
Salve, feche o arquivo e teste a transferẽncia.
Bom pessoal, este é o segundo post sobre como utilizar Ruby com XMPP. O terceiro vou mostrar como fazer aplicações web em tempo real usando as mesmas ferramentas. Até lá!


