=begin rdoc
=violet/response.rb
==Summary
this module handle servers messages. Main method/class are Response.parse and Response::Base::ServerRsp (well documented).
you should only use Response.parse with the server's message (xml) as argument, it returns a ServerRsp instance
from the corresponding class (see all the ServerRsp subclass). with a ServerRsp instance you can use :
[ r.has_x? ]
return +true+ if r has at least one element of name "x", return +false+ otherwhise.
[ r.has_many_xs? ]
return +true+ if r has more than one element of name "x", return +false+ otherwhise.
[ r.x ]
find the first xml element of name x and return it's text if any, or a hash of it's options
[ r.x ]
find all xml element of name x and return an Array of their text if any, or their options.
[ r.good? ]
return +true+ if the response is not an error, +false+ otherwhise.
[ r.bad? ]
return +true+ if the response is an error, +false+ otherwhise.
==Examples :
>> rsp = Response.parse('')
=> # ... >>
>> rsp.good?
=> true
>> rsp.has_message?
=> false
>> rsp.message
NameError: undefined local variable or method message for # ... >>
>> rsp.has_blacklist?
=> true
>> rsp.blacklist
=> {:nb=>"2"}
>> rsp.pseudo
=> {:name=>"toto"}
>> rsp.has_many_pseudos?
=> true
>> rsp.pseudos
=> [{:name=>"toto"}, {:name=>"titi"}]
==Low Level
if you want to access to the REXML::Document object of a ServerRsp you can either use rsp.xml or use ServerRsp#get_all method.
=end
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) or $:.include?(File.expand_path(File.dirname(__FILE__)))
require 'helpers.rb'
module Response
# ProtocolExcepion are raised if server return a unknown response.
#
# see http://api.nabaztag.com/docs/home.html#messages
class ProtocolExcepion < StandardError; end
# contains some basic stuff, abstract class etc. they're used internaly and you should not access this module.
module Base
require 'rexml/document'
# base class used to handle Violet server's responses.
#
# We want to access to all xml elements easily, like the powerful ActiveRecord 'find' function. This class
# provide virtual accessor and predicate for elements. If you have a ServerRsp, rsp.has_message? will return
# +true+ if rsp has a 'message' element, and rsp.has_many_messages? will return +true+ if rsp has more than one
# 'message' element. rsp.message will return the first message element and rsp.messages will return an Array
# that contains all message elements of rsp (see doc of Response module for examples).
class ServerRsp
# create a ServerRsp with the raw argument. raw must be the xml text of the server's response. if the xml
# is malformed, a REXML::ParseException will be raised.
def initialize raw
@xml = REXML::Document.new raw
end
# It's possible to access the REXML::Document object if needed, but try to use virtual accessors and get_all
# if possible.
attr_reader :xml
# return +true+ if the response is not an error, +false+ otherwhise.
def good?
self.is_a? GoodServerRsp
end
# return +true+ if the response is an error, +false+ otherwhise.
def bad?
self.is_a? BadServerRsp
end
# ==Summary
# get all xml's element that match name.
#
#
# ==Arguments
# name : name of the element you want to fetch (see examples)
# block : a block of code that take a REXML::Element in parameter. if no block is given, it return an Array of REXML::Element.
#
#
# ==Examples
# Side effect
# >> rsp = Response.parse('')
# => # ... >>
# >> rsp.get_all(:myLang) do |e|
# >> puts "you can use '#{e.attribute('lang').value}'"
# >> end
# you can use 'fr'
# you can use 'us'
# you can use 'uk'
# you can use 'de'
# => [nil, nil, nil, nil]
#
# usage of returned value
# >> langs = rsp.get_all(:myLang) { |e| e.attribute('lang').value }
# => ["fr", "us", "uk", "de"]
def get_all name
# REXML::XPath.match(@xml, element).collect do |e| <= this one is for a recursive search.
@xml.root.elements.collect(name.to_s) do |e|
if block_given? then yield(e) else e end
end
end
# here some magic code :)
def method_missing(name) #:nodoc:
# this method to transforme REXML::Element into text or hash
filter = Proc.new do |e|
e.text || e.attributes.to_hash
end
# raise an error when there are no results and method_missing is not a question
check = Proc.new do |ary|
if ary.empty?
raise NameError.new("undefined local variable or method #{$1} for #{self.inspect}")
else
ary
end
end
case name.to_s
when /^has_many_(.+)s\?$/ then get_all($1).size > 1
when /^has_(.+)\?$/ then get_all($1).size > 0
when /(.*)s$/ then check.call( get_all($1).collect(&filter) )
when /(.*)/ then check.call( get_all($1).collect(&filter) ).first
end
end
end
# superclass of error messages. They're 'simple', they only have a message and a comment element.
# see http://api.nabaztag.com/docs/home.html#messages
class BadServerRsp < ServerRsp; end
# superclass of messages with infos (no error).
class GoodServerRsp < ServerRsp; end
end # module Response::Base
# superclass of messages with no infos.
class EmptyServerRsp < Base::ServerRsp; end
#
# Errors messages from server
#
# Too much requests sent
# rsp.message # => "ABUSESENDING"
# rsp.comment # => "Too much message sending,try later"
class AbuseSending < Base::BadServerRsp; end
# Wrong token or serial number
# rsp.message # => "NOGOODTOKENORSERIAL"
# rsp.comment # => "Your token or serial number are not correct !"
class NoGoodTokenOrSerial < Base::BadServerRsp; end
# Wrong music id (either not in your personal MP3s list or not existing)
# rsp.message # => "MESSAGENOTSEND"
# rsp.comment # => "Your idmessage is not correct or is private"
class MessageNotSend < Base::BadServerRsp; end
# Nabcast not posted because music id is not part of your personal MP3s or because the nabcast id does not belong
# to you or is not existing
# rsp.message # => "NABCASTNOTSEND"
# rsp.comment # => "Your idmessage is private"
#
# rsp.message # => "NABCASTNOTSEND"
# rsp.comment # => "Your nabcast id is not correct or is private"
class NabCastNotSend < Base::BadServerRsp; end
# Message not sent
# rsp.message # => "MESSAGENOTSEND"
# rsp.comment # => "Your message could not be sent"
class MessageNotSend < Base::BadServerRsp; end
# TTS creation problem or TTS not send
# rsp.message # => "TTSNOTSEND"
# rsp.comment # => "Your text could not be sent"
#
# rsp.message # => "TTSNOTSEND"
# rsp.comment # => "Your text could not be sent"
class TtsNotSend < Base::BadServerRsp; end
# Choregraphy message not sent because the "chor" command was incorrect
# rsp.message # => "CHORNOTSEND"
# rsp.comment # => "Your chor could not be sent (bad chor)"
class ChorNotSend < Base::BadServerRsp; end
# Ears position not sent because the given position is incorrect
# rsp.message # => "EARPOSITIONNOTSEND"
# rsp.comment # => "Your ears command could not be sent"
class EarPositionNotSend < Base::BadServerRsp; end
# URL was not sent (api_stream)
# rsp.message # => "WEBRADIONOTSEND"
# rsp.comment # => "Your webradio could not be sent"
class WebRadioNotSend < Base::BadServerRsp; end
# urlList parameter missing (api_stream)
# rsp.message # => "NOCORRECTPARAMETERS"
# rsp.comment # => "Please check urlList parameter !"
class NoCorrectParameters < Base::BadServerRsp; end
# The rabbit is not a Nabaztag/tag
# rsp.message # => "NOTV2RABBIT"
# rsp.comment # => "V2 rabbit can use this action"
class NotV2Rabbit < Base::BadServerRsp; end
#
# Infos messages from server
#
# Nabcast posted
# rsp.message # => "NABCASTSEND"
# rsp.comment # => "Your nabcast has been sent"
class NabCastSend < Base::GoodServerRsp; end
# Message sent
# rsp.message # => "MESSAGESEND"
# rsp.comment # => "Your message has been sent"
class MessageSend < Base::GoodServerRsp; end
# TTS message sent
# rsp.message # => "TTSSEND"
# rsp.comment # => "Your text has been sent"
class TtsSend < Base::GoodServerRsp; end
# Choregraphy message sent
# rsp.message # => "CHORSEND"
# rsp.comment # => "Your chor has been sent"
class ChorSend < Base::GoodServerRsp; end
# Ears position sent
# rsp.message # => "EARPOSITIONSEND"
# rsp.comment # => "Your ears command has been sent"
class EarPositionSend < Base::GoodServerRsp; end
# URL was sent (api_stream)
# rsp.message # => "WEBRADIOSEND"
# rsp.comment # => "Your webradio has been sent"
class WebRadioSend < Base::GoodServerRsp; end
# Getting the ears position
# rsp.message # => "POSITIONEAR"
# rsp.leftposition # => "8"
# rsp.rightposition # => "10"
class PositionEar < Base::GoodServerRsp; end
# Preview the TTS or music (with music id) without sending it
# rsp.message # => "LINKPREVIEW"
# rsp.comment # => "XXXX"
class LinkPreview < Base::GoodServerRsp; end
# Getting friends list
# rsp.listfriend # => {:nb=>"2"}
# rsp.friends # => [{:name=>"toto"}, {:name=>"titi"}]
class ListFriend < Base::GoodServerRsp; end
# a count and the list of the messages in your inbox
# rsp.listreceivedmsg # => {:nb=>"1"}
# rsp.msg # => {:title=>"my message", :date=>"today 11:59", :from=>"toto", :url=>"broad/001/948.mp3"}
class ListReceivedMsg < Base::GoodServerRsp; end
# the timezone in which your Nabaztag is set
# rsp.timezone # => "(GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London"
class Timezone < Base::GoodServerRsp; end
# the signature defined for the Nabaztag
# rsp.signature # => "i'm Ruby powered !"
class Signature < Base::GoodServerRsp; end
# a count and the list of people in your blacklist
# rsp.blacklist # => {:nb=>"2"}
# rsp.pseudos # => [{:name=>"toto"}, {:name=>"tata"}]
class Blacklist < Base::GoodServerRsp; end
# to know if the Nabaztag is sleeping
# rsp.rabbitSleep # => "YES"
class RabbitSleep < Base::GoodServerRsp; end
# to know if the Nabaztag is a Nabaztag (V1) or a Nabaztag/tag (V2)
# rsp.rabbitVersion # => "V2"
class RabbitVersion < Base::GoodServerRsp; end
# a list of all supported languages/voices for TTS (text to speach) engine
# rsp.voiceListTTS # => {:nb=>"2"}
# rsp.voices # => [{:command=>"claire22k", :lang=>"fr"}, {:command=>"helga22k", :lang=>"de"}]
class VoiceListTts < Base::GoodServerRsp; end
# the name of the Nabaztag
# rsp.rabbitName # => "nabmaster"
class RabbitName < Base::GoodServerRsp; end
# Get the languages selected for the Nabaztag
# rsp.langListUser # => {"nb"=>"4"}
# rsp.myLang # => {:lang=>"fr"}
# rsp.myLangs # => [{:lang=>"fr"}, {:lang=>"us"}, {:lang=>"uk"}, {:lang=>"de"}]
class LangListUser < Base::GoodServerRsp; end
# Command has been send.
# rsp.message # => "COMMANDSEND"
# rsp.comment # => "You rabbit will change status"
#
# see Request::SET_RABBIT_ASLEEP and Request::SET_RABBIT_AWAKE
class CommandSend < Base::GoodServerRsp; end
# ==Summary
# parse given raw (xml text) and return a new ServerRsp from the corresponding class.
#
# Violet messages aren't
# easy to identify, because there is not id. So we have to study the xml content if there are no message
# element (easier to detect the response type).
#
#
# ==Arguments
# the xml response of the Violet Server.
#
#
# ==Exceptions
# this method raise a ProtocolExcepion if it's fail to detect the
# kind of the server's response.
#
#
# ==Examples
# >> rsp = Response.parse('YES')
# => # ... >>
# >> rsp.class
# => Response::RabbitSleep
#
# >> rsp = Response.parse('V1')
# => # ... >>
# >> rsp.class
# => Response::RabbitVersion
#
def Response.parse raw
tmp = Base::ServerRsp.new raw # we shouldn't create ServerRsp instances, but act as if you didn't see ;)
klassname = if raw =~ %r|\s*|i
'EmptyServerRsp'
elsif tmp.has_message?
/^#{tmp.message}$/i
else
/^#{tmp.xml.root.elements[1].name}$/i # REXML::Elements#[] has index 1-based and not 0-based, so we really fetch the first element's name
end
klass = nil
begin
klass = Helpers.constantize "#{self}::#{Response.constants.grep(klassname).first}"
raise if klass.nil?
rescue
raise ProtocolExcepion.new("unknown server's response : #{raw}")
end
klass.new raw
end
end # module Response