ln-xmpp4r 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. data/CHANGELOG +91 -0
  2. data/COPYING +340 -0
  3. data/LICENSE +59 -0
  4. data/README.rdoc +113 -0
  5. data/README_ruby19.txt +43 -0
  6. data/Rakefile +252 -0
  7. data/data/doc/xmpp4r/examples/advanced/adventure/README +56 -0
  8. data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
  9. data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
  10. data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
  11. data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
  12. data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +424 -0
  13. data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
  14. data/data/doc/xmpp4r/examples/advanced/fileserve.rb +346 -0
  15. data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
  16. data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
  17. data/data/doc/xmpp4r/examples/advanced/migrate.rb +88 -0
  18. data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
  19. data/data/doc/xmpp4r/examples/advanced/pep-aggregator/index.xsl +235 -0
  20. data/data/doc/xmpp4r/examples/advanced/pep-aggregator/pep-aggregator.rb +147 -0
  21. data/data/doc/xmpp4r/examples/advanced/recvfile.rb +84 -0
  22. data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +129 -0
  23. data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
  24. data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
  25. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
  26. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
  27. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
  28. data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +109 -0
  29. data/data/doc/xmpp4r/examples/advanced/xmpping.rb +146 -0
  30. data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +14 -0
  31. data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
  32. data/data/doc/xmpp4r/examples/basic/client.rb +70 -0
  33. data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
  34. data/data/doc/xmpp4r/examples/basic/echo.rb +37 -0
  35. data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
  36. data/data/doc/xmpp4r/examples/basic/mass_sender.rb +68 -0
  37. data/data/doc/xmpp4r/examples/basic/muc_owner_config.rb +12 -0
  38. data/data/doc/xmpp4r/examples/basic/mucinfo.rb +41 -0
  39. data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +82 -0
  40. data/data/doc/xmpp4r/examples/basic/register.rb +42 -0
  41. data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
  42. data/data/doc/xmpp4r/examples/basic/roster.rb +44 -0
  43. data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
  44. data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
  45. data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +171 -0
  46. data/data/doc/xmpp4r/examples/basic/send_vcard.rb +67 -0
  47. data/data/doc/xmpp4r/examples/basic/tune_client.rb +56 -0
  48. data/data/doc/xmpp4r/examples/basic/tune_server.rb +58 -0
  49. data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
  50. data/lib/xmpp4r.rb +116 -0
  51. data/lib/xmpp4r/base64.rb +32 -0
  52. data/lib/xmpp4r/bytestreams.rb +15 -0
  53. data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +319 -0
  54. data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +257 -0
  55. data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +31 -0
  56. data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +54 -0
  57. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +152 -0
  58. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +86 -0
  59. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +198 -0
  60. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +65 -0
  61. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +79 -0
  62. data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +170 -0
  63. data/lib/xmpp4r/bytestreams/iq/si.rb +206 -0
  64. data/lib/xmpp4r/callbacks.rb +133 -0
  65. data/lib/xmpp4r/caps.rb +1 -0
  66. data/lib/xmpp4r/caps/c.rb +67 -0
  67. data/lib/xmpp4r/caps/helper/generator.rb +160 -0
  68. data/lib/xmpp4r/caps/helper/helper.rb +84 -0
  69. data/lib/xmpp4r/client.rb +344 -0
  70. data/lib/xmpp4r/command/helper/responder.rb +53 -0
  71. data/lib/xmpp4r/command/iq/command.rb +154 -0
  72. data/lib/xmpp4r/component.rb +103 -0
  73. data/lib/xmpp4r/connection.rb +223 -0
  74. data/lib/xmpp4r/dataforms.rb +5 -0
  75. data/lib/xmpp4r/dataforms/x/data.rb +297 -0
  76. data/lib/xmpp4r/debuglog.rb +63 -0
  77. data/lib/xmpp4r/delay.rb +5 -0
  78. data/lib/xmpp4r/delay/x/delay.rb +99 -0
  79. data/lib/xmpp4r/discovery.rb +8 -0
  80. data/lib/xmpp4r/discovery/helper/helper.rb +58 -0
  81. data/lib/xmpp4r/discovery/helper/responder.rb +165 -0
  82. data/lib/xmpp4r/discovery/iq/discoinfo.rb +211 -0
  83. data/lib/xmpp4r/discovery/iq/discoitems.rb +147 -0
  84. data/lib/xmpp4r/errors.rb +284 -0
  85. data/lib/xmpp4r/feature_negotiation.rb +5 -0
  86. data/lib/xmpp4r/feature_negotiation/iq/feature.rb +28 -0
  87. data/lib/xmpp4r/framework/base.rb +55 -0
  88. data/lib/xmpp4r/framework/bot.rb +148 -0
  89. data/lib/xmpp4r/httpbinding.rb +5 -0
  90. data/lib/xmpp4r/httpbinding/client.rb +275 -0
  91. data/lib/xmpp4r/idgenerator.rb +37 -0
  92. data/lib/xmpp4r/iq.rb +221 -0
  93. data/lib/xmpp4r/jid.rb +167 -0
  94. data/lib/xmpp4r/last.rb +2 -0
  95. data/lib/xmpp4r/last/helper/helper.rb +37 -0
  96. data/lib/xmpp4r/last/iq/last.rb +67 -0
  97. data/lib/xmpp4r/location.rb +2 -0
  98. data/lib/xmpp4r/location/helper/helper.rb +56 -0
  99. data/lib/xmpp4r/location/location.rb +179 -0
  100. data/lib/xmpp4r/message.rb +180 -0
  101. data/lib/xmpp4r/muc.rb +14 -0
  102. data/lib/xmpp4r/muc/helper/mucbrowser.rb +92 -0
  103. data/lib/xmpp4r/muc/helper/mucclient.rb +462 -0
  104. data/lib/xmpp4r/muc/helper/simplemucclient.rb +332 -0
  105. data/lib/xmpp4r/muc/iq/mucadmin.rb +23 -0
  106. data/lib/xmpp4r/muc/iq/mucadminitem.rb +20 -0
  107. data/lib/xmpp4r/muc/iq/mucowner.rb +15 -0
  108. data/lib/xmpp4r/muc/item.rb +143 -0
  109. data/lib/xmpp4r/muc/x/muc.rb +70 -0
  110. data/lib/xmpp4r/muc/x/mucuserinvite.rb +60 -0
  111. data/lib/xmpp4r/muc/x/mucuseritem.rb +36 -0
  112. data/lib/xmpp4r/presence.rb +232 -0
  113. data/lib/xmpp4r/pubsub.rb +8 -0
  114. data/lib/xmpp4r/pubsub/children/configuration.rb +86 -0
  115. data/lib/xmpp4r/pubsub/children/event.rb +49 -0
  116. data/lib/xmpp4r/pubsub/children/item.rb +35 -0
  117. data/lib/xmpp4r/pubsub/children/items.rb +53 -0
  118. data/lib/xmpp4r/pubsub/children/node_config.rb +48 -0
  119. data/lib/xmpp4r/pubsub/children/publish.rb +38 -0
  120. data/lib/xmpp4r/pubsub/children/retract.rb +41 -0
  121. data/lib/xmpp4r/pubsub/children/subscription.rb +62 -0
  122. data/lib/xmpp4r/pubsub/children/subscription_config.rb +67 -0
  123. data/lib/xmpp4r/pubsub/children/unsubscribe.rb +48 -0
  124. data/lib/xmpp4r/pubsub/helper/nodebrowser.rb +129 -0
  125. data/lib/xmpp4r/pubsub/helper/nodehelper.rb +156 -0
  126. data/lib/xmpp4r/pubsub/helper/oauth_service_helper.rb +90 -0
  127. data/lib/xmpp4r/pubsub/helper/servicehelper.rb +456 -0
  128. data/lib/xmpp4r/pubsub/iq/pubsub.rb +19 -0
  129. data/lib/xmpp4r/query.rb +15 -0
  130. data/lib/xmpp4r/reliable.rb +168 -0
  131. data/lib/xmpp4r/rexmladdons.rb +157 -0
  132. data/lib/xmpp4r/roster.rb +7 -0
  133. data/lib/xmpp4r/roster/helper/roster.rb +522 -0
  134. data/lib/xmpp4r/roster/iq/roster.rb +215 -0
  135. data/lib/xmpp4r/roster/x/roster.rb +138 -0
  136. data/lib/xmpp4r/rpc.rb +2 -0
  137. data/lib/xmpp4r/rpc/helper/client.rb +123 -0
  138. data/lib/xmpp4r/rpc/helper/server.rb +74 -0
  139. data/lib/xmpp4r/rpc/helper/xmlrpcaddons.rb +67 -0
  140. data/lib/xmpp4r/rpc/iq/rpc.rb +23 -0
  141. data/lib/xmpp4r/sasl.rb +248 -0
  142. data/lib/xmpp4r/semaphore.rb +38 -0
  143. data/lib/xmpp4r/stream.rb +599 -0
  144. data/lib/xmpp4r/streamparser.rb +85 -0
  145. data/lib/xmpp4r/test/listener_mocker.rb +118 -0
  146. data/lib/xmpp4r/tune.rb +2 -0
  147. data/lib/xmpp4r/tune/helper/helper.rb +58 -0
  148. data/lib/xmpp4r/tune/tune.rb +113 -0
  149. data/lib/xmpp4r/vcard.rb +6 -0
  150. data/lib/xmpp4r/vcard/helper/vcard.rb +84 -0
  151. data/lib/xmpp4r/vcard/iq/vcard.rb +109 -0
  152. data/lib/xmpp4r/version.rb +7 -0
  153. data/lib/xmpp4r/version/helper/responder.rb +72 -0
  154. data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
  155. data/lib/xmpp4r/version/iq/version.rb +105 -0
  156. data/lib/xmpp4r/x.rb +37 -0
  157. data/lib/xmpp4r/xhtml.rb +1 -0
  158. data/lib/xmpp4r/xhtml/html.rb +115 -0
  159. data/lib/xmpp4r/xmpp4r.rb +20 -0
  160. data/lib/xmpp4r/xmppelement.rb +168 -0
  161. data/lib/xmpp4r/xmppstanza.rb +162 -0
  162. data/setup.rb +1586 -0
  163. data/test/bytestreams/tc_ibb.rb +188 -0
  164. data/test/bytestreams/tc_socks5bytestreams.rb +114 -0
  165. data/test/caps/tc_helper.rb +158 -0
  166. data/test/dataforms/tc_data.rb +81 -0
  167. data/test/delay/tc_xdelay.rb +51 -0
  168. data/test/discovery/tc_responder.rb +91 -0
  169. data/test/last/tc_helper.rb +75 -0
  170. data/test/lib/assert_equal_xml.rb +14 -0
  171. data/test/lib/clienttester.rb +149 -0
  172. data/test/muc/tc_muc_mucclient.rb +834 -0
  173. data/test/muc/tc_muc_simplemucclient.rb +114 -0
  174. data/test/muc/tc_mucowner.rb +50 -0
  175. data/test/pubsub/tc_helper.rb +785 -0
  176. data/test/pubsub/tc_nodeconfig.rb +61 -0
  177. data/test/pubsub/tc_subscriptionconfig.rb +41 -0
  178. data/test/reliable/tc_disconnect_cleanup.rb +334 -0
  179. data/test/reliable/tc_disconnect_exception.rb +37 -0
  180. data/test/reliable/tc_listener_mocked_test.rb +68 -0
  181. data/test/reliable/tc_reliable_connection.rb +31 -0
  182. data/test/roster/tc_helper.rb +524 -0
  183. data/test/roster/tc_iqqueryroster.rb +173 -0
  184. data/test/roster/tc_xroster.rb +73 -0
  185. data/test/rpc/tc_helper.rb +96 -0
  186. data/test/tc_callbacks.rb +129 -0
  187. data/test/tc_class_names.rb +146 -0
  188. data/test/tc_client.rb +30 -0
  189. data/test/tc_errors.rb +146 -0
  190. data/test/tc_idgenerator.rb +30 -0
  191. data/test/tc_iq.rb +113 -0
  192. data/test/tc_iqquery.rb +31 -0
  193. data/test/tc_jid.rb +204 -0
  194. data/test/tc_message.rb +131 -0
  195. data/test/tc_presence.rb +150 -0
  196. data/test/tc_rexml.rb +139 -0
  197. data/test/tc_stream.rb +167 -0
  198. data/test/tc_streamComponent.rb +95 -0
  199. data/test/tc_streamError.rb +129 -0
  200. data/test/tc_streamSend.rb +59 -0
  201. data/test/tc_streamparser.rb +125 -0
  202. data/test/tc_xmppstanza.rb +135 -0
  203. data/test/ts_xmpp4r.rb +44 -0
  204. data/test/tune/tc_helper_recv.rb +82 -0
  205. data/test/tune/tc_helper_send.rb +74 -0
  206. data/test/tune/tc_tune.rb +79 -0
  207. data/test/vcard/tc_helper.rb +49 -0
  208. data/test/vcard/tc_iqvcard.rb +62 -0
  209. data/test/version/tc_helper.rb +60 -0
  210. data/test/version/tc_iqqueryversion.rb +97 -0
  211. data/test/xhtml/tc_html.rb +41 -0
  212. data/tools/gen_requires.bash +31 -0
  213. data/tools/xmpp4r-gemspec-test.rb +11 -0
  214. data/xmpp4r.gemspec +238 -0
  215. metadata +280 -0
@@ -0,0 +1,19 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'xmpp4r/iq'
6
+
7
+ module Jabber
8
+ module PubSub
9
+ NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
10
+ class IqPubSub < XMPPElement
11
+ name_xmlns 'pubsub', NS_PUBSUB
12
+ force_xmlns true
13
+ end
14
+ class IqPubSubOwner < XMPPElement
15
+ name_xmlns 'pubsub', NS_PUBSUB + '#owner'
16
+ force_xmlns true
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'xmpp4r/xmppelement'
6
+
7
+ module Jabber
8
+ ##
9
+ # A class used to build/parse IQ Query requests/responses
10
+ #
11
+ class IqQuery < XMPPElement
12
+ name_xmlns 'query'
13
+ force_xmlns true
14
+ end
15
+ end
@@ -0,0 +1,168 @@
1
+ require 'xmpp4r/stream'
2
+
3
+ module Jabber
4
+ module Reliable
5
+
6
+ class Connection < Jabber::Client
7
+ def initialize(full_jid, config)
8
+ super(full_jid)
9
+ @servers = config[:servers]
10
+ @port = config[:port] || 5222
11
+ @max_retry = config[:max_retry] || 30
12
+ @retry_sleep = config[:retry_sleep] || 2
13
+ if(@servers.nil? or @servers.empty?)
14
+ @servers = [@jid.domain]
15
+ end
16
+ end
17
+
18
+ def connect
19
+ retry_count = 0
20
+ server_to_use = nil
21
+ server_pool = @servers.dup.sort{ rand <=> rand }
22
+ begin
23
+ server_to_use = server_pool.shift
24
+ server_pool.push(server_to_use)
25
+
26
+ Jabber::debuglog "timeout will be: #{@retry_sleep.to_f}"
27
+ Timeout.timeout(@retry_sleep.to_f){
28
+ Jabber::debuglog "trying to connect to #{server_to_use}"
29
+ super(server_to_use, @port)
30
+ }
31
+
32
+ Jabber::debuglog self.jid.to_s + " connected to " + server_to_use.to_s
33
+ Jabber::debuglog "out of possible servers " + @servers.inspect
34
+ rescue Exception, Timeout::Error => e
35
+ Jabber::warnlog "#{server_to_use} error: #{e.inspect}. Will attempt to reconnect in #{@retry_sleep}"
36
+ sleep(@retry_sleep.to_f)
37
+ if(retry_count >= @max_retry.to_i)
38
+ Jabber::warnlog "reached max retry count on exception, failing"
39
+ raise e
40
+ end
41
+ retry_count += 1
42
+ retry
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ class Listener
49
+ def initialize(full_jid, password, config, &block)
50
+ @on_message_block = block
51
+ @full_jid = full_jid
52
+ @config = config
53
+ @password = password
54
+ @max_retry = config[:max_retry] || 30
55
+ end
56
+
57
+ def setup_connection
58
+ @connection = Connection.new(@full_jid, @config)
59
+ if @on_message_block
60
+ @connection.add_message_callback(&@on_message_block)
61
+ else
62
+ @connection.add_message_callback do |msg|
63
+ self.on_message(msg)
64
+ end
65
+ end
66
+
67
+ #We could just reconnect in @connection.on_exception,
68
+ #but by raising into this seperate thread, we avoid growing our stack trace
69
+ @reconnection_thread = Thread.new do
70
+ first_run = true
71
+ begin
72
+ self.start unless first_run
73
+ loop do
74
+ sleep(1)
75
+ Thread.pass
76
+ end
77
+ rescue => e
78
+ first_run = false
79
+ retry
80
+ end
81
+ end
82
+ @exception_handlers = []
83
+ @connection.on_exception do |e, connection, where_failed|
84
+ self.run_exception_handlers(e, connection, where_failed)
85
+ end
86
+ end
87
+
88
+ def run_exception_handlers(e, connection, where_failed)
89
+ @exception_handlers.each do |ex_handler|
90
+ ex_handler.call(e, connection, where_failed)
91
+ end
92
+ if where_failed == :sending
93
+ @message_to_send_on_reconnect = @message_now_sending
94
+ end
95
+ if where_failed != :close && !@connection.is_connected?
96
+ @reconnection_thread.raise(e)
97
+ end
98
+ end
99
+
100
+ def add_exception_handler(&block)
101
+ @exception_handlers << block
102
+ end
103
+
104
+ def start
105
+ setup_connection unless @connection
106
+ connect
107
+ auth
108
+ send_presence
109
+ if @message_to_send_on_reconnect
110
+ send_message(@message_to_send_on_reconnect)
111
+ end
112
+ @message_to_send_on_reconnect = nil
113
+ end
114
+
115
+ #Stop the listener. (close the connection)
116
+ def stop
117
+ @connection.close if @connection and @connection.is_connected?
118
+ @connection = nil
119
+ end
120
+
121
+ def connect
122
+ @connection.connect
123
+ end
124
+
125
+ def auth
126
+ @connection.auth(@password)
127
+ end
128
+
129
+ def send_presence
130
+ presence_message = @config[:presence_message]
131
+ if presence_message && !presence_message.empty?
132
+ @connection.send(Jabber::Presence.new.set_show(:chat).set_status(presence_message))
133
+ end
134
+ end
135
+
136
+ #TODO: test and fix situation where we get disconnected while sending but then successfully reconnect
137
+ # (and make sure in such cases we resent)
138
+ def send_message(message)
139
+ unless @connection
140
+ raise ::ArgumentError, "Can't send messages while listener is stopped. Plase 'start' the listener first."
141
+ end
142
+ retry_count = 0
143
+ begin
144
+ while(not @connection.is_connected?)
145
+ #wait
146
+ Thread.pass
147
+ end
148
+ @message_now_sending = message
149
+ @connection.send(message)
150
+ return true #true, message was sent
151
+ rescue => e
152
+ if e.is_a?(Interrupt)
153
+ raise e
154
+ end
155
+ if(retry_count > @max_retry.to_i)
156
+ Jabber::debuglog "reached max retry count on message re-send, failing"
157
+ raise e
158
+ end
159
+ retry_count += 1
160
+ Jabber::debuglog "retrying message send.." + e.inspect
161
+ retry
162
+ end
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,157 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'rexml/document'
6
+ require 'rexml/parsers/xpathparser'
7
+ require 'rexml/source'
8
+
9
+ # Turn $VERBOSE off to suppress warnings about redefinition
10
+ oldverbose = $VERBOSE
11
+ $VERBOSE = false
12
+
13
+ # REXML : Adds custom helper methods to the REXML module.
14
+ #
15
+ module REXML
16
+
17
+ # this class adds a few helper methods to REXML::Element
18
+ class Element
19
+
20
+ def each_elements(*els, &block)
21
+ els.inject([ ]) do |res, e|
22
+ res + each_element(e, &block)
23
+ end
24
+ end
25
+
26
+ ##
27
+ # Replaces or adds a child element of name <tt>e</tt> with text <tt>t</tt>.
28
+ def replace_element_text(e, t)
29
+ el = first_element(e)
30
+ if el.nil?
31
+ el = REXML::Element.new(e)
32
+ add_element(el)
33
+ end
34
+ if t
35
+ el.text = t
36
+ end
37
+ self
38
+ end
39
+
40
+ ##
41
+ # Returns first element of name <tt>e</tt>
42
+ def first_element(e)
43
+ each_element(e) { |el| return el }
44
+ return nil
45
+ end
46
+
47
+ ##
48
+ # Returns text of first element of name <tt>e</tt>
49
+ def first_element_text(e)
50
+ el = first_element(e)
51
+ if el
52
+ return el.text
53
+ else
54
+ return nil
55
+ end
56
+ end
57
+
58
+ # This method does exactly the same thing as add(), but it can be
59
+ # overriden by subclasses to provide on-the-fly object creations.
60
+ # For example, if you import a REXML::Element of name 'plop', and you
61
+ # have a Plop class that subclasses REXML::Element, with typed_add you
62
+ # can get your REXML::Element to be "magically" converted to Plop.
63
+ def typed_add(e)
64
+ add(e)
65
+ end
66
+
67
+ ##
68
+ # import this element's children and attributes
69
+ def import(xmlelement)
70
+ if @name and @name != xmlelement.name
71
+ raise "Trying to import an #{xmlelement.name} to a #{@name} !"
72
+ end
73
+ add_attributes(xmlelement.attributes.clone)
74
+ @context = xmlelement.context
75
+ xmlelement.each do |e|
76
+ if e.kind_of? REXML::Element
77
+ typed_add(e.deep_clone)
78
+ elsif e.kind_of? REXML::Text
79
+ add_text(e.value)
80
+ else
81
+ add(e.clone)
82
+ end
83
+ end
84
+ self
85
+ end
86
+
87
+ def self.import(xmlelement)
88
+ self.new(xmlelement.name).import(xmlelement)
89
+ end
90
+
91
+ ##
92
+ # Deletes one or more children elements,
93
+ # not just one like REXML::Element#delete_element
94
+ def delete_elements(element)
95
+ while(delete_element(element)) do end
96
+ end
97
+
98
+ ##
99
+ # Test for equality of two elements, useful for assert_equal in
100
+ # test cases. Tries to parse String o as XML.
101
+ #
102
+ # See Test::Unit::Assertions
103
+ def ==(o)
104
+ return false unless self.kind_of? REXML::Element
105
+ if o.kind_of? REXML::Element
106
+ # Ok
107
+ elsif o.kind_of? String
108
+ # Parse o
109
+ begin
110
+ o = REXML::Document.new(o).root
111
+ rescue REXML::ParseException
112
+ return false
113
+ end
114
+ else
115
+ # Cannot compare with anything other than Elements or Strings
116
+ return false
117
+ end
118
+
119
+ return false unless name == o.name
120
+
121
+ attributes.each_attribute do |attr|
122
+ return false unless attr.value == o.attributes[attr.name]
123
+ end
124
+
125
+ o.attributes.each_attribute do |attr|
126
+ return false unless attributes[attr.name] == attr.value
127
+ end
128
+
129
+ children.each_with_index do |child,i|
130
+ return false unless child == o.children[i]
131
+ end
132
+
133
+ return true
134
+ end
135
+
136
+ end # class Element
137
+
138
+ # FIXME : Is this still needed now that we're a bit past Ruby 1.8.3??
139
+ # Very dirty fix for the :progress problem in REXML from Ruby 1.8.3
140
+ # http://www.germane-software.com/projects/rexml/ticket/34
141
+ # the fix proposed in REXML changeset 1145 only fixes this for pipes, not for
142
+ # TCP sockets, so we have to keep this.
143
+ class IOSource
144
+ def position
145
+ 0
146
+ end
147
+
148
+ def current_line
149
+ [0, 0, ""]
150
+ end
151
+ end # class IOSource
152
+
153
+ end # module REXML
154
+
155
+ # Restore the old $VERBOSE setting
156
+ $VERBOSE = oldverbose
157
+
@@ -0,0 +1,7 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'xmpp4r/roster/iq/roster.rb'
6
+ require 'xmpp4r/roster/helper/roster.rb'
7
+ require 'xmpp4r/roster/x/roster.rb'
@@ -0,0 +1,522 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'xmpp4r/callbacks'
6
+ require 'thread'
7
+ require 'xmpp4r/roster/iq/roster'
8
+
9
+ module Jabber
10
+ module Roster
11
+ ##
12
+ # The Roster helper intercepts <tt><iq/></tt> stanzas with Jabber::IqQueryRoster
13
+ # and <tt><presence/></tt> stanzas, but provides cbs which allow the programmer
14
+ # to keep track of updates.
15
+ #
16
+ # A thread for any received stanza is spawned, so the user can invoke
17
+ # accept_subscription et al in the callback blocks, without stopping
18
+ # the current (= parser) thread when waiting for a reply.
19
+ class Helper
20
+ ##
21
+ # All items in your roster
22
+ # items:: [Hash] ([JID] => [Roster::Helper::RosterItem])
23
+ attr_reader :items
24
+
25
+ ##
26
+ # Initialize a new Roster helper
27
+ #
28
+ # Registers its cbs (prio = 120, ref = self)
29
+ #
30
+ # Request a roster
31
+ # (Remember to send initial presence afterwards!)
32
+ #
33
+ # The initialization will not wait for the roster being received,
34
+ # use wait_for_roster.
35
+ #
36
+ # <b>Attention:</b> If you send presence and receive presences
37
+ # before the roster has arrived, the Roster helper will let them
38
+ # pass through and does *not* keep them!
39
+ def initialize(stream, startnow = true)
40
+ @stream = stream
41
+ @items = {}
42
+ @items_lock = Mutex.new
43
+ @roster_wait = Semaphore.new
44
+ @query_cbs = CallbackList.new
45
+ @update_cbs = CallbackList.new
46
+ @presence_cbs = CallbackList.new
47
+ @subscription_cbs = CallbackList.new
48
+ @subscription_request_cbs = CallbackList.new
49
+
50
+ # Register cbs
51
+ stream.add_iq_callback(120, self) { |iq|
52
+ if iq.query.kind_of?(IqQueryRoster)
53
+ Thread.new do
54
+ Thread.current.abort_on_exception = true
55
+ handle_iq_query_roster(iq)
56
+ end
57
+
58
+ true
59
+ else
60
+ false
61
+ end
62
+ }
63
+ stream.add_presence_callback(120, self) { |pres|
64
+ Thread.new do
65
+ Thread.current.abort_on_exception = true
66
+ handle_presence(pres)
67
+ end
68
+ }
69
+ get_roster if startnow
70
+ end
71
+
72
+ def get_roster
73
+ # Request the roster
74
+ rosterget = Iq.new_rosterget
75
+ @stream.send(rosterget)
76
+ end
77
+
78
+ ##
79
+ # Wait for first roster query result to arrive
80
+ def wait_for_roster
81
+ @roster_wait.wait
82
+ @roster_wait.run
83
+ end
84
+
85
+ ##
86
+ # Add a callback to be called when a query has been processed
87
+ #
88
+ # Because update callbacks are called for each roster item,
89
+ # this may be appropriate to notify that *anything* has updated.
90
+ #
91
+ # Arguments for callback block: The received <tt><iq/></tt> stanza
92
+ def add_query_callback(prio = 0, ref = nil, &block)
93
+ @query_cbs.add(prio, ref, block)
94
+ end
95
+
96
+ ##
97
+ # Add a callback for Jabber::Roster::Helper::RosterItem updates
98
+ #
99
+ # Note that this will be called much after initialization
100
+ # for the answer of the initial roster request
101
+ #
102
+ # The block receives two objects:
103
+ # * the old Jabber::Roster::Helper::RosterItem
104
+ # * the new Jabber::Roster::Helper::RosterItem
105
+ def add_update_callback(prio = 0, ref = nil, &block)
106
+ @update_cbs.add(prio, ref, block)
107
+ end
108
+
109
+ ##
110
+ # Add a callback for Jabber::Presence updates
111
+ #
112
+ # This will be called for <tt><presence/></tt> stanzas for known RosterItems.
113
+ # Unknown JIDs may still pass and can be caught via Jabber::Stream#add_presence_callback.
114
+ #
115
+ # The block receives three objects:
116
+ # * the Jabber::Roster::Helper::RosterItem
117
+ # * the old Jabber::Presence (or nil)
118
+ # * the new Jabber::Presence (or nil)
119
+ def add_presence_callback(prio = 0, ref = nil, &block)
120
+ @presence_cbs.add(prio, ref, block)
121
+ end
122
+
123
+ ##
124
+ # Add a callback for subscription updates,
125
+ # which will be called upon receiving a <tt><presence/></tt> stanza
126
+ # with type:
127
+ # * :subscribed
128
+ # * :unsubscribe
129
+ # * :unsubscribed
130
+ #
131
+ # The block receives two objects:
132
+ # * the Jabber::Roster::Helper::RosterItem (or nil)
133
+ # * the <tt><presence/></tt> stanza
134
+ def add_subscription_callback(prio = 0, ref = nil, &block)
135
+ @subscription_cbs.add(prio, ref, block)
136
+ end
137
+
138
+ ##
139
+ # Add a callback for subscription requests,
140
+ # which will be called upon receiving a <tt><presence type='subscribe'/></tt> stanza
141
+ #
142
+ # The block receives two objects:
143
+ # * the Jabber::Roster::Helper::RosterItem (or nil)
144
+ # * the <tt><presence/></tt> stanza
145
+ #
146
+ # Response to this event can be taken with accept_subscription
147
+ # and decline_subscription.
148
+ #
149
+ # Example usage:
150
+ # my_roster.add_subscription_request_callback do |item,presence|
151
+ # if accept_subscription_requests
152
+ # my_roster.accept_subscription(presence.from)
153
+ # else
154
+ # my_roster.decline_subscription(presence.from)
155
+ # end
156
+ # end
157
+ def add_subscription_request_callback(prio = 0, ref = nil, &block)
158
+ @subscription_request_cbs.add(prio, ref, block)
159
+ end
160
+
161
+ private
162
+
163
+ ##
164
+ # Handle received <tt><iq/></tt> stanzas,
165
+ # used internally
166
+ def handle_iq_query_roster(iq)
167
+ # If the <iq/> contains <error/> we just ignore that
168
+ # and assume an empty roster
169
+ iq.query.each_element('item') do |item|
170
+ olditem, newitem = nil, nil
171
+
172
+ @items_lock.synchronize {
173
+ olditem = @items[item.jid]
174
+
175
+ # Handle deletion of item
176
+ if item.subscription == :remove
177
+ @items.delete(item.jid)
178
+ else
179
+ newitem = @items[item.jid] = RosterItem.new(@stream).import(item)
180
+ end
181
+ }
182
+ @update_cbs.process(olditem, newitem)
183
+ end
184
+
185
+ @roster_wait.run
186
+ @query_cbs.process(iq)
187
+ end
188
+
189
+ ##
190
+ # Handle received <tt><presence/></tt> stanzas,
191
+ # used internally
192
+ def handle_presence(pres)
193
+ item = self[pres.from]
194
+
195
+ if [:subscribed, :unsubscribe, :unsubscribed].include?(pres.type)
196
+ @subscription_cbs.process(item, pres)
197
+ true
198
+
199
+ elsif pres.type == :subscribe
200
+ @subscription_request_cbs.process(item, pres)
201
+ true
202
+
203
+ else
204
+ unless item.nil?
205
+ update_presence(item, pres)
206
+ true # Callback consumed stanza
207
+ else
208
+ false # Callback did not consume stanza
209
+ end
210
+ end
211
+ end
212
+
213
+ ##
214
+ # Update the presence of an item,
215
+ # used internally
216
+ #
217
+ # Callbacks are called here
218
+ def update_presence(item, pres)
219
+
220
+ # This requires special handling, to announce all resources offline
221
+ if pres.from.resource.nil? and pres.type == :error
222
+ oldpresences = []
223
+ item.each_presence do |oldpres|
224
+ oldpresences << oldpres
225
+ end
226
+
227
+ item.add_presence(pres)
228
+ oldpresences.each { |oldpres|
229
+ @presence_cbs.process(item, oldpres, pres)
230
+ }
231
+ else
232
+ oldpres = item.presence(pres.from).nil? ?
233
+ nil :
234
+ Presence.new.import(item.presence(pres.from))
235
+
236
+ item.add_presence(pres)
237
+ @presence_cbs.process(item, oldpres, pres)
238
+ end
239
+ end
240
+
241
+ public
242
+
243
+ ##
244
+ # Get an item by jid
245
+ #
246
+ # If not available tries to look for it with the resource stripped
247
+ def [](jid)
248
+ jid = JID.new(jid) unless jid.kind_of? JID
249
+
250
+ @items_lock.synchronize {
251
+ if @items.has_key?(jid)
252
+ @items[jid]
253
+ elsif @items.has_key?(jid.strip)
254
+ @items[jid.strip]
255
+ else
256
+ nil
257
+ end
258
+ }
259
+ end
260
+
261
+ ##
262
+ # Returns the list of RosterItems which, stripped, are equal to the
263
+ # one you are looking for.
264
+ def find(jid)
265
+ jid = JID.new(jid) unless jid.kind_of? JID
266
+
267
+ j = jid.strip
268
+ l = {}
269
+ @items_lock.synchronize {
270
+ @items.each_pair do |k, v|
271
+ l[k] = v if k.strip == j
272
+ end
273
+ }
274
+ l
275
+ end
276
+
277
+ ##
278
+ # Groups in this Roster,
279
+ # sorted by name
280
+ #
281
+ # Contains +nil+ if there are ungrouped items
282
+ # result:: [Array] containing group names (String)
283
+ def groups
284
+ res = []
285
+ @items_lock.synchronize {
286
+ @items.each_pair do |jid,item|
287
+ res += item.groups
288
+ res += [nil] if item.groups == []
289
+ end
290
+ }
291
+ res.uniq.sort { |a,b| a.to_s <=> b.to_s }
292
+ end
293
+
294
+ ##
295
+ # Get items in a group
296
+ #
297
+ # When group is nil, return ungrouped items
298
+ # group:: [String] Group name
299
+ # result:: Array of [RosterItem]
300
+ def find_by_group(group)
301
+ res = []
302
+ @items_lock.synchronize {
303
+ @items.each_pair do |jid,item|
304
+ res.push(item) if item.groups.include?(group)
305
+ res.push(item) if item.groups == [] and group.nil?
306
+ end
307
+ }
308
+ res
309
+ end
310
+
311
+ ##
312
+ # Add a user to your roster
313
+ #
314
+ # Threading is encouraged as the function waits for
315
+ # a result. ServerError is thrown upon error.
316
+ #
317
+ # See Jabber::Roster::Helper::RosterItem#subscribe for details
318
+ # about subscribing. (This method isn't used here but the
319
+ # same functionality applies.)
320
+ #
321
+ # If the item is already in the local roster
322
+ # it will simply send itself
323
+ # jid:: [JID] to add
324
+ # iname:: [String] Optional item name
325
+ # subscribe:: [Boolean] Whether to subscribe to this jid
326
+ def add(jid, iname=nil, subscribe=false)
327
+ if self[jid]
328
+ self[jid].send
329
+ else
330
+ request = Iq.new_rosterset
331
+ request.query.add(Jabber::Roster::RosterItem.new(jid, iname))
332
+ @stream.send_with_id(request)
333
+ # Adding to list is handled by handle_iq_query_roster
334
+ end
335
+
336
+ if subscribe
337
+ # Actually the item *should* already be known now,
338
+ # but we do it manually to exclude conditions.
339
+ pres = Presence.new.set_type(:subscribe).set_to(jid.strip)
340
+ @stream.send(pres)
341
+ end
342
+ end
343
+
344
+ ##
345
+ # Accept a subscription request
346
+ # * Sends a <presence type='subscribed'/> stanza
347
+ # * Adds the contact to your roster
348
+ # jid:: [JID] of contact
349
+ # iname:: [String] Optional roster item name
350
+ def accept_subscription(jid, iname=nil)
351
+ pres = Presence.new.set_type(:subscribed).set_to(jid.strip)
352
+ @stream.send(pres)
353
+
354
+ unless self[jid.strip]
355
+ request = Iq.new_rosterset
356
+ request.query.add(Jabber::Roster::RosterItem.new(jid.strip, iname))
357
+ @stream.send_with_id(request)
358
+ end
359
+ end
360
+
361
+ ##
362
+ # Decline a subscription request
363
+ # * Sends a <presence type='unsubscribed'/> stanza
364
+ def decline_subscription(jid)
365
+ pres = Presence.new.set_type(:unsubscribed).set_to(jid.strip)
366
+ @stream.send(pres)
367
+ end
368
+
369
+ ##
370
+ # These are extensions to RosterItem to carry presence information.
371
+ # This information is *not* stored in XML!
372
+ class RosterItem < Jabber::Roster::RosterItem
373
+ ##
374
+ # Tracked (online) presences of this RosterItem
375
+ attr_reader :presences
376
+
377
+ ##
378
+ # Initialize an empty RosterItem
379
+ def initialize(stream)
380
+ super()
381
+ @stream = stream
382
+ @presences = []
383
+ @presences_lock = Mutex.new
384
+ end
385
+
386
+ ##
387
+ # Send the updated RosterItem to the server,
388
+ # i.e. if you modified iname, groups, ...
389
+ def send
390
+ request = Iq.new_rosterset
391
+ request.query.add(self)
392
+ @stream.send(request)
393
+ end
394
+
395
+ ##
396
+ # Remove item
397
+ #
398
+ # This cancels both subscription *from* the contact to you
399
+ # and from you *to* the contact.
400
+ #
401
+ # The methods waits for a roster push from the server (success)
402
+ # or throws ServerError upon failure.
403
+ def remove
404
+ request = Iq.new_rosterset
405
+ request.query.add(Jabber::Roster::RosterItem.new(jid, nil, :remove))
406
+ @stream.send_with_id(request)
407
+ # Removing from list is handled by Roster#handle_iq_query_roster
408
+ end
409
+
410
+ ##
411
+ # Is any presence of this person on-line?
412
+ #
413
+ # (Or is there any presence? Unavailable presences are
414
+ # deleted.)
415
+ def online?
416
+ @presences_lock.synchronize {
417
+ @presences.select { |pres|
418
+ pres.type.nil?
419
+ }.size > 0
420
+ }
421
+ end
422
+
423
+ ##
424
+ # Iterate through all received <tt><presence/></tt> stanzas
425
+ def each_presence(&block)
426
+ # Don't lock here, we don't know what block does...
427
+ @presences.each { |pres|
428
+ yield(pres)
429
+ }
430
+ end
431
+
432
+ ##
433
+ # Get specific presence
434
+ # jid:: [JID] Full JID
435
+ def presence(jid)
436
+ @presences_lock.synchronize {
437
+ @presences.each { |pres|
438
+ return(pres) if pres.from == jid
439
+ }
440
+ }
441
+ nil
442
+ end
443
+
444
+ ##
445
+ # Add presence and sort presences
446
+ # (unless type is :unavailable or :error)
447
+ #
448
+ # This overwrites previous stanzas with the same destination
449
+ # JID to keep track of resources. Presence stanzas with
450
+ # <tt>type == :unavailable</tt> or <tt>type == :error</tt> will
451
+ # be deleted as this indicates that this resource has gone
452
+ # offline.
453
+ #
454
+ # If <tt>type == :error</tt> and the presence's origin has no
455
+ # specific resource the contact is treated completely offline.
456
+ def add_presence(newpres)
457
+ @presences_lock.synchronize {
458
+ # Delete old presences with the same JID
459
+ @presences.delete_if do |pres|
460
+ pres.from == newpres.from or pres.from.resource.nil?
461
+ end
462
+
463
+ if newpres.type == :error and newpres.from.resource.nil?
464
+ # Replace by single error presence
465
+ @presences = [newpres]
466
+ else
467
+ # Add new presence
468
+ @presences.push(newpres)
469
+ end
470
+
471
+ @presences.sort!
472
+ }
473
+ end
474
+
475
+ ##
476
+ # Send subscription request to the user
477
+ #
478
+ # The block given to Jabber::Roster::Roster#add_update_callback will
479
+ # be called, carrying the RosterItem with ask="subscribe"
480
+ #
481
+ # This function returns immediately after sending the subscription
482
+ # request and will not wait of approval or declination as it may
483
+ # take months for the contact to decide. ;-)
484
+ def subscribe
485
+ pres = Presence.new.set_type(:subscribe).set_to(jid.strip)
486
+ @stream.send(pres)
487
+ end
488
+
489
+ ##
490
+ # Unsubscribe from a contact's presence
491
+ #
492
+ # This method waits for a presence with type='unsubscribed'
493
+ # from the contact. It may throw ServerError upon failure.
494
+ #
495
+ # subscription attribute of the item is *from* or *none*
496
+ # afterwards. As long as you don't remove that item and
497
+ # subscription='from' the contact is subscribed to your
498
+ # presence.
499
+ def unsubscribe
500
+ pres = Presence.new.set_type(:unsubscribe).set_to(jid.strip)
501
+ @stream.send(pres) { |answer|
502
+ answer.type == :unsubscribed and
503
+ answer.from.strip == pres.to
504
+ }
505
+ end
506
+
507
+ ##
508
+ # Deny the contact to see your presence.
509
+ #
510
+ # This method will not wait and returns immediately
511
+ # as you will need no confirmation for this action.
512
+ #
513
+ # Though, you will get a roster update for that item,
514
+ # carrying either subscription='to' or 'none'.
515
+ def cancel_subscription
516
+ pres = Presence.new.set_type(:unsubscribed).set_to(jid)
517
+ @stream.send(pres)
518
+ end
519
+ end
520
+ end #Class Roster
521
+ end #Module Roster
522
+ end #Module Jabber