lib/blather/client/client.rb in blather-0.4.1 vs lib/blather/client/client.rb in blather-0.4.2
- old
+ new
@@ -1,152 +1,252 @@
require File.join(File.dirname(__FILE__), *%w[.. .. blather])
module Blather #:nodoc:
- class Client #:nodoc:
+ # = Blather Client
+ #
+ # Blather's Client class provides a set of helpers for working with common XMPP tasks such as setting up and starting
+ # the connection, settings status, registering and dispatching filters and handlers and roster management.
+ #
+ # Client can be used separately from the DSL if you'd like to implement your own DSL
+ # Here's the echo example using the client without the DSL:
+ #
+ # require 'blather/client/client'
+ # client = Client.setup 'echo@jabber.local', 'echo'
+ #
+ # client.register_handler(:ready) { puts "Connected ! send messages to #{client.jid.stripped}." }
+ #
+ # client.register_handler :subscription, :request? do |s|
+ # client.write s.approve!
+ # end
+ #
+ # client.register_handler :message, :chat?, :body => 'exit' do |m|
+ # client.write Blather::Stanza::Message.new(m.from, 'Exiting...')
+ # client.close
+ # end
+ #
+ # client.register_handler :message, :chat?, :body do |m|
+ # client.write Blather::Stanza::Message.new(m.from, "You sent: #{m.body}")
+ # end
+ #
+ class Client
attr_reader :jid,
:roster
- def initialize
+ ##
+ # Initialize and setup the client
+ # * +jid+ - the JID to login with
+ # * +password+ - password associated with the JID
+ # * +host+ - hostname or IP to connect to. If nil the stream will look one up based on the domain in the JID
+ # * +port+ - port to connect to
+ def self.setup(jid, password, host = nil, port = nil)
+ self.new.setup(jid, password, host, port)
+ end
+
+ def initialize # :nodoc:
@state = :initializing
@status = Stanza::Presence::Status.new
@handlers = {}
@tmp_handlers = {}
+ @filters = {:before => [], :after => []}
@roster = Roster.new self
setup_initial_handlers
end
- def jid=(new_jid)
- @jid = JID.new new_jid
- end
-
+ ##
+ # Get the current status. Taken from the +state+ attribute of Status
def status
@status.state
end
+ ##
+ # Set the status. Status can be set with either a single value or an array containing
+ # [state, message, to].
def status=(state)
state, msg, to = state
status = Stanza::Presence::Status.new state, msg
status.to = to
@status = status unless to
write status
end
- def setup?
- @setup.is_a? Array
- end
-
- def setup(jid, password, host = nil, port = nil)
- @jid = JID.new(jid)
- @setup = [@jid, password]
- @setup << host if host
- @setup << port if port
- self
- end
-
+ ##
+ # Start the connection.
def run
raise 'not setup!' unless setup?
klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
@stream = klass.start self, *@setup
end
+ ##
+ # Register a filter to be run before or after the handler chain is run.
+ # * +type+ - the type of filter. Must be +:before+ or +:after+
+ # * +guards+ - guards that should be checked before the filter is called
+ def register_filter(type, handler = nil, *guards, &filter)
+ raise "Invalid filter: #{type}. Must be :before or :after" unless [:before, :after].include?(type)
+ @filters[type] << [guards, handler, filter]
+ end
+
+ ##
+ # Register a temporary handler. Temporary handlers are based on the ID of the JID and live
+ # only until a stanza with said ID is received.
+ # * +id+ - the ID of the stanza that should be handled
def register_tmp_handler(id, &handler)
@tmp_handlers[id] = handler
end
+ ##
+ # Register a handler
+ # * +type+ - the handler type. Should be registered in Stanza.handler_list. Blather will log a warning if it's not.
+ # * +guards+ - the list of guards that must be verified before the handler will be called
def register_handler(type, *guards, &handler)
- check_guards guards
+ check_handler type, guards
@handlers[type] ||= []
@handlers[type] << [guards, handler]
end
+ ##
+ # Write data to the stream
def write(stanza)
@stream.send(stanza) if @stream
end
+ ##
+ # Helper that will create a temporary handler for the stanza being sent before writing it to the stream.
+ #
+ # client.write_with_handler(stanza) { |s| "handle stanza here" }
+ #
+ # is equivalent to:
+ #
+ # client.register_tmp_handler(stanza.id) { |s| "handle stanza here" }
+ # client.write stanza
def write_with_handler(stanza, &handler)
register_tmp_handler stanza.id, &handler
write stanza
end
- def post_init
- self.jid.node ? client_post_init : ready!
- end
-
+ ##
+ # Close the connection
def close
@stream.close_connection_after_writing
end
- def unbind
+ def post_init # :nodoc:
+ self.jid.node ? client_post_init : ready!
+ end
+
+ def unbind # :nodoc:
EM.stop if EM.reactor_running?
end
- def receive_data(stanza)
- if handler = @tmp_handlers.delete(stanza.id)
- handler.call stanza
- else
- stanza.handler_heirarchy.each do |type|
- break if call_handler_for(type, stanza)# && (stanza.is_a?(BlatherError) || stanza.type == :iq)
- end
+ def receive_data(stanza) # :nodoc:
+ catch(:halt) do
+ run_filters :before, stanza
+ handle_stanza stanza
+ run_filters :after, stanza
end
end
+ def jid=(new_jid) # :nodoc :
+ @jid = JID.new new_jid
+ end
+
+ def setup? # :nodoc:
+ @setup.is_a? Array
+ end
+
+ def setup(jid, password, host = nil, port = nil) # :nodoc:
+ @jid = JID.new(jid)
+ @setup = [@jid, password]
+ @setup << host if host
+ @setup << port if port
+ self
+ end
+
protected
- def setup_initial_handlers
+ def check_handler(type, guards)
+ Blather.logger.warn "Handler for type \"#{type}\" will never be called as it's not a registered type" unless current_handlers.include?(type)
+ check_guards guards
+ end
+
+ def current_handlers
+ [:ready] + Stanza.handler_list + BlatherError.handler_list
+ end
+
+ def setup_initial_handlers # :nodoc:
register_handler :error do |err|
raise err
end
register_handler :iq, :type => [:get, :set] do |iq|
- write(StanzaError.new(iq, 'service-unavailable', :cancel).to_node)
+ write StanzaError.new(iq, 'service-unavailable', :cancel).to_node
end
register_handler :status do |status|
roster[status.from].status = status if roster[status.from]
+ nil
end
register_handler :roster do |node|
roster.process node
end
end
- def ready!
+ def ready! # :nodoc:
@state = :ready
call_handler_for :ready, nil
end
- def client_post_init
+ def client_post_init # :nodoc:
write_with_handler Stanza::Iq::Roster.new do |node|
roster.process node
write @status
ready!
end
end
- def call_handler_for(type, stanza)
- if @handlers[type]
- @handlers[type].find do |guards, handler|
- if guards.first.is_a?(String)
- unless (result = stanza.find(*guards)).empty?
- handler.call(stanza, result)
- end
- elsif !guarded?(guards, stanza)
- handler.call(stanza)
- end
+ def run_filters(type, stanza) # :nodoc:
+ @filters[type].each do |guards, handler, filter|
+ next if handler && !stanza.handler_heirarchy.include?(handler)
+ catch(:pass) { call_handler filter, guards, stanza }
+ end
+ end
+
+ def handle_stanza(stanza) # :nodoc:
+ if handler = @tmp_handlers.delete(stanza.id)
+ handler.call stanza
+ else
+ stanza.handler_heirarchy.each do |type|
+ break if call_handler_for(type, stanza)
end
end
end
+ def call_handler_for(type, stanza) # :nodoc:
+ return unless handler = @handlers[type]
+ handler.find do |guards, handler|
+ catch(:pass) { call_handler handler, guards, stanza }
+ end
+ end
+
+ def call_handler(handler, guards, stanza) # :nodoc:
+ if guards.first.respond_to?(:to_str) && !(result = stanza.find(*guards)).empty?
+ handler.call(stanza, result)
+ elsif !guarded?(guards, stanza)
+ handler.call(stanza)
+ end
+ end
+
##
# If any of the guards returns FALSE this returns true
# the logic is reversed to allow short circuiting
# (why would anyone want to loop over more values than necessary?)
- def guarded?(guards, stanza)
+ def guarded?(guards, stanza) # :nodoc:
guards.find do |guard|
case guard
when Symbol
!stanza.__send__(guard)
when Array
@@ -154,14 +254,13 @@
!guard.detect { |condition| !guarded?([condition], stanza) }
when Hash
# return FALSE unless any inequality is found
guard.find do |method, test|
value = stanza.__send__(method)
- case test
- when Regexp
- !value.to_s.match(test)
- when Array
+ if test.class.respond_to?(:last_match)
+ !(test =~ value)
+ elsif test.is_a?(Array)
!test.include? value
else
test != value
end
end
@@ -169,10 +268,10 @@
!guard.call(stanza)
end
end
end
- def check_guards(guards)
+ def check_guards(guards) # :nodoc:
guards.each do |guard|
case guard
when Array
guard.each { |g| check_guards([g]) }
when Symbol, Proc, Hash, String