xmpp4r 0.5 → 0.5.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 (41) hide show
  1. data/CHANGELOG +21 -0
  2. data/README.rdoc +29 -38
  3. data/Rakefile +11 -18
  4. data/data/doc/xmpp4r/examples/advanced/recvfile.rb +5 -4
  5. data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +2 -0
  6. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +1 -0
  7. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +1 -1
  8. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +4 -1
  9. data/lib/xmpp4r/caps/helper/generator.rb +2 -2
  10. data/lib/xmpp4r/client.rb +18 -4
  11. data/lib/xmpp4r/connection.rb +8 -5
  12. data/lib/xmpp4r/delay/x/delay.rb +1 -1
  13. data/lib/xmpp4r/entity_time.rb +6 -0
  14. data/lib/xmpp4r/entity_time/iq.rb +45 -0
  15. data/lib/xmpp4r/entity_time/responder.rb +57 -0
  16. data/lib/xmpp4r/httpbinding/client.rb +178 -44
  17. data/lib/xmpp4r/message.rb +46 -0
  18. data/lib/xmpp4r/muc/helper/mucclient.rb +8 -1
  19. data/lib/xmpp4r/observable.rb +9 -0
  20. data/lib/xmpp4r/observable/contact.rb +61 -0
  21. data/lib/xmpp4r/observable/helper.rb +389 -0
  22. data/lib/xmpp4r/observable/observable_thing.rb +191 -0
  23. data/lib/xmpp4r/observable/pubsub.rb +211 -0
  24. data/lib/xmpp4r/observable/subscription.rb +57 -0
  25. data/lib/xmpp4r/observable/thread_store.rb +65 -0
  26. data/lib/xmpp4r/pubsub/children/unsubscribe.rb +16 -1
  27. data/lib/xmpp4r/pubsub/helper/servicehelper.rb +48 -14
  28. data/lib/xmpp4r/rexmladdons.rb +46 -6
  29. data/lib/xmpp4r/roster/helper/roster.rb +19 -9
  30. data/lib/xmpp4r/sasl.rb +1 -1
  31. data/lib/xmpp4r/stream.rb +2 -2
  32. data/lib/xmpp4r/xmpp4r.rb +1 -1
  33. data/test/bytestreams/tc_socks5bytestreams.rb +2 -2
  34. data/test/entity_time/tc_responder.rb +65 -0
  35. data/test/roster/tc_helper.rb +2 -3
  36. data/test/tc_message.rb +52 -1
  37. data/test/tc_stream.rb +2 -2
  38. data/test/tc_streamComponent.rb +11 -1
  39. data/test/ts_xmpp4r.rb +11 -2
  40. data/xmpp4r.gemspec +20 -9
  41. metadata +41 -35
@@ -0,0 +1,65 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ #
3
+ # This file's copyright (c) 2009 by Pablo Lorenzzoni <pablo@propus.com.br>
4
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
5
+ # Website::http://home.gna.org/xmpp4r/
6
+ #
7
+ # Class ThreadStore
8
+ class ThreadStore
9
+ attr_reader :cycles, :max
10
+
11
+ # Create a new ThreadStore.
12
+ #
13
+ # max:: max number of threads to store (when exhausted, older threads will
14
+ # just be killed and discarded). Default is 100. If 0 or negative no
15
+ # threads will be discarded until #keep is called.
16
+ def initialize(max = 100)
17
+ @store = []
18
+ @max = max > 0 ? max : 0
19
+ @cycles = 0
20
+ @killer_thread = Thread.new do
21
+ loop do
22
+ sleep 2 while @store.empty?
23
+ sleep 1
24
+ @store.each_with_index do |thread, i|
25
+ th = @store.delete_at(i) if thread.nil? or ! thread.alive?
26
+ th = nil
27
+ end
28
+ @cycles += 1
29
+ end
30
+ end
31
+ end
32
+
33
+ def inspect # :nodoc:
34
+ sprintf("#<%s:0x%x @max=%d, @size=%d @cycles=%d>", self.class.name, __id__, @max, size, @cycles)
35
+ end
36
+
37
+ # Add a new thread to the ThreadStore
38
+ def add(thread)
39
+ if thread.instance_of?(Thread) and thread.respond_to?(:alive?)
40
+ @store << thread
41
+ keep(@max) if @max > 0
42
+ end
43
+ end
44
+
45
+ # Keep only the number passed of threads
46
+ #
47
+ # n:: number of threads to keep (default to @max if @max > 0)
48
+ def keep(n = nil)
49
+ if n.nil?
50
+ raise ArgumentError, "You need to pass the number of threads to keep!" if @max == 0
51
+ n = @max
52
+ end
53
+ @store.shift.kill while @store.length > n
54
+ end
55
+
56
+ # Kill all threads
57
+ def kill!
58
+ @store.shift.kill while @store.length > 0
59
+ end
60
+
61
+ # How long is our ThreadStore
62
+ def size; @store.length; end
63
+
64
+ end # of class ThreadStore
65
+
@@ -10,10 +10,11 @@ module Jabber
10
10
  # Unsubscribe
11
11
  class Unsubscribe < XMPPElement
12
12
  name_xmlns 'unsubscribe'
13
- def initialize(myjid=nil,mynode=nil)
13
+ def initialize(myjid=nil,mynode=nil,mysubid=nil)
14
14
  super()
15
15
  jid = myjid
16
16
  node = mynode
17
+ subid = mysubid
17
18
  end
18
19
 
19
20
  ##
@@ -43,6 +44,20 @@ module Jabber
43
44
  def node=(mynode)
44
45
  attributes['node'] = mynode
45
46
  end
47
+
48
+ ##
49
+ # shows the subid
50
+ # return:: [String]
51
+ def subid
52
+ attributes['subid']
53
+ end
54
+
55
+ ##
56
+ # sets the subid
57
+ # =:: [String]
58
+ def subid=(mysubid)
59
+ attributes['subid'] = mysubid
60
+ end
46
61
  end
47
62
  end
48
63
  end
@@ -85,6 +85,20 @@ module Jabber
85
85
 
86
86
  res
87
87
  end
88
+
89
+ ##
90
+ # get subids for a passed node
91
+ # return:: [Array] of subids
92
+ def get_subids_for(node)
93
+ ret = []
94
+ get_subscriptions_from_all_nodes.each do |subscription|
95
+ if subscription.node == node
96
+ ret << subscription.subid
97
+ end
98
+ end
99
+ return ret
100
+ end
101
+
88
102
  ##
89
103
  # subscribe to a node
90
104
  # node:: [String]
@@ -113,15 +127,26 @@ module Jabber
113
127
  # subid:: [String] or nil (not supported)
114
128
  # return:: true
115
129
  def unsubscribe_from(node, subid=nil)
116
- iq = basic_pubsub_query(:set)
117
- unsub = PubSub::Unsubscribe.new
118
- unsub.node = node
119
- unsub.jid = @stream.jid.strip
120
- iq.pubsub.add(unsub)
121
- ret = false
122
- @stream.send_with_id(iq) { |reply|
123
- ret = reply.kind_of?(Jabber::Iq) and reply.type == :result
124
- } # @stream.send_with_id(iq)
130
+ ret = []
131
+ if subid.nil?
132
+ subids = get_subids_for(node)
133
+ else
134
+ subids = [ subid ]
135
+ end
136
+ subids << nil if subids.empty?
137
+ subids.each do |sid|
138
+ iq = basic_pubsub_query(:set)
139
+ unsub = PubSub::Unsubscribe.new
140
+ unsub.node = node
141
+ unsub.jid = @stream.jid.strip
142
+ unsub.subid = sid
143
+ iq.pubsub.add(unsub)
144
+ res = false
145
+ @stream.send_with_id(iq) { |reply|
146
+ res = reply.kind_of?(Jabber::Iq) and reply.type == :result
147
+ } # @stream.send_with_id(iq)
148
+ ret << res
149
+ end
125
150
  ret
126
151
  end
127
152
 
@@ -129,11 +154,21 @@ module Jabber
129
154
  # gets all items from a pubsub node
130
155
  # node:: [String]
131
156
  # count:: [Fixnum]
157
+ # subid:: [String]
132
158
  # return:: [Hash] { id => [Jabber::PubSub::Item] }
133
- def get_items_from(node, count=nil)
159
+ def get_items_from(node, count=nil, subid=nil)
160
+ if subid.nil?
161
+ # Hm... no subid passed. Let's see if we can provide one.
162
+ subids = get_subids_for(node)
163
+ if ! subids.empty?
164
+ # If more than one, sorry. We'll just respect the first.
165
+ subid = subids[0]
166
+ end
167
+ end
134
168
  iq = basic_pubsub_query(:get)
135
169
  items = Jabber::PubSub::Items.new
136
170
  items.max_items = count
171
+ items.subid = subid unless subid.nil? # if subid is still nil, we haven't any... why bother?
137
172
  items.node = node
138
173
  iq.pubsub.add(items)
139
174
  res = nil
@@ -157,7 +192,7 @@ module Jabber
157
192
  # return:: true
158
193
  def publish_item_to(node,item)
159
194
  iq = basic_pubsub_query(:set)
160
- publish = iq.pubsub.add(REXML::Element.new('publish'))
195
+ publish = iq.pubsub.add(REXML::Element.new('publish'))
161
196
  publish.attributes['node'] = node
162
197
 
163
198
  if item.kind_of?(Jabber::PubSub::Item)
@@ -212,13 +247,12 @@ module Jabber
212
247
  @stream.send_with_id(iq)
213
248
  end
214
249
 
215
-
216
250
  ##
217
251
  # purges all items on a persistent node
218
252
  # node:: [String]
219
253
  # return:: true
220
254
  def purge_items_from(node)
221
- iq = basic_pubsub_query(:set)
255
+ iq = basic_pubsub_query(:set, true)
222
256
  purge = REXML::Element.new('purge')
223
257
  purge.attributes['node'] = node
224
258
  iq.pubsub.add(purge)
@@ -354,7 +388,7 @@ module Jabber
354
388
  res = []
355
389
  if reply.pubsub.first_element('subscriptions').attributes['node'] == node
356
390
  reply.pubsub.first_element('subscriptions').each_element('subscription') { |subscription|
357
- res << PubSub::Subscription.import(subscription)
391
+ res << PubSub::Subscription.import(subscription)
358
392
  }
359
393
  end
360
394
  end
@@ -25,10 +25,11 @@ module REXML
25
25
 
26
26
  ##
27
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)
28
+ def replace_element_text(e, t, namespace = nil)
29
+ el = first_element(e, namespace)
30
30
  if el.nil?
31
31
  el = REXML::Element.new(e)
32
+ el.add_namespace(namespace)
32
33
  add_element(el)
33
34
  end
34
35
  if t
@@ -36,18 +37,42 @@ module REXML
36
37
  end
37
38
  self
38
39
  end
40
+
41
+ ##
42
+ # Replaces or adds a child element of name <tt>e</tt> with content of <tt>t</tt>.
43
+ def replace_element_content(e, c, namespace = nil)
44
+ el = first_element(e, namespace)
45
+ if el.nil?
46
+ el = REXML::Element.new(e)
47
+ el.add_namespace(namespace)
48
+ add_element(el)
49
+ end
50
+ if c
51
+ el.children.each do |ch|
52
+ ch.remove
53
+ end
54
+ c.root.children.each do |ch|
55
+ el.add ch
56
+ end
57
+ end
58
+ self
59
+ end
39
60
 
40
61
  ##
41
62
  # Returns first element of name <tt>e</tt>
42
- def first_element(e)
43
- each_element(e) { |el| return el }
63
+ def first_element(e, namespace = nil)
64
+ if namespace
65
+ each_element_with_attribute("xmlns", namespace, 1, e) { |el| return el }
66
+ else
67
+ each_element(e) { |el| return el }
68
+ end
44
69
  return nil
45
70
  end
46
71
 
47
72
  ##
48
73
  # Returns text of first element of name <tt>e</tt>
49
- def first_element_text(e)
50
- el = first_element(e)
74
+ def first_element_text(e, namespace = nil)
75
+ el = first_element(e, namespace)
51
76
  if el
52
77
  return el.text
53
78
  else
@@ -55,6 +80,21 @@ module REXML
55
80
  end
56
81
  end
57
82
 
83
+ ##
84
+ # This method works like <tt>first_element_text</tt> except that it
85
+ # returns content of all children, not just the value of the first
86
+ # child text element.
87
+ #
88
+ # Returns content of first element of name <tt>e</tt>
89
+ def first_element_content(e, namespace = nil)
90
+ el = first_element(e, namespace)
91
+ if el
92
+ return el.children.join
93
+ else
94
+ return nil
95
+ end
96
+ end
97
+
58
98
  # This method does exactly the same thing as add(), but it can be
59
99
  # overriden by subclasses to provide on-the-fly object creations.
60
100
  # For example, if you import a REXML::Element of name 'plop', and you
@@ -229,9 +229,9 @@ module Jabber
229
229
  @presence_cbs.process(item, oldpres, pres)
230
230
  }
231
231
  else
232
- oldpres = item.presence(pres.from).nil? ?
232
+ oldpres = item.presence_of(pres.from).nil? ?
233
233
  nil :
234
- Presence.new.import(item.presence(pres.from))
234
+ Presence.new.import(item.presence_of(pres.from))
235
235
 
236
236
  item.add_presence(pres)
237
237
  @presence_cbs.process(item, oldpres, pres)
@@ -431,8 +431,8 @@ module Jabber
431
431
 
432
432
  ##
433
433
  # Get specific presence
434
- # jid:: [JID] Full JID
435
- def presence(jid)
434
+ # jid:: [JID] Full JID with resource
435
+ def presence_of(jid)
436
436
  @presences_lock.synchronize {
437
437
  @presences.each { |pres|
438
438
  return(pres) if pres.from == jid
@@ -441,15 +441,25 @@ module Jabber
441
441
  nil
442
442
  end
443
443
 
444
+ ##
445
+ # Get presence of highest-priority available resource of this person
446
+ #
447
+ # Returns <tt>nil</tt> if contact is offline
448
+ def presence
449
+ @presences_lock.synchronize {
450
+ @presences.select { |pres|
451
+ pres.type.nil?
452
+ }.max { |pres1, pres2| (pres1.priority || 0) <=> (pres2.priority || 0) }
453
+ }
454
+ end
455
+
444
456
  ##
445
457
  # Add presence and sort presences
446
458
  # (unless type is :unavailable or :error)
447
459
  #
448
460
  # 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.
461
+ # JID to keep track of resources. Old presence stanzas with
462
+ # <tt>type == :unavailable</tt> will be deleted.
453
463
  #
454
464
  # If <tt>type == :error</tt> and the presence's origin has no
455
465
  # specific resource the contact is treated completely offline.
@@ -457,7 +467,7 @@ module Jabber
457
467
  @presences_lock.synchronize {
458
468
  # Delete old presences with the same JID
459
469
  @presences.delete_if do |pres|
460
- pres.from == newpres.from or pres.from.resource.nil?
470
+ pres.from == newpres.from or pres.from.resource.nil? or pres.type == :unavailable
461
471
  end
462
472
 
463
473
  if newpres.type == :error and newpres.from.resource.nil?
@@ -177,7 +177,7 @@ module Jabber
177
177
  response['nc'] = '00000001'
178
178
  response['qop'] = 'auth'
179
179
  response['digest-uri'] = "xmpp/#{@stream.jid.domain}"
180
- response['response'] = response_value(@stream.jid.node, @stream.jid.domain, response['digest-uri'], password, @nonce, response['cnonce'], response['qop'], response['authzid'])
180
+ response['response'] = response_value(@stream.jid.node, response['realm'], response['digest-uri'], password, @nonce, response['cnonce'], response['qop'], response['authzid'])
181
181
  response.each { |key,value|
182
182
  unless %w(nc qop response charset).include? key
183
183
  response[key] = "\"#{value}\""
@@ -577,13 +577,13 @@ module Jabber
577
577
  # In some cases, we might lost count of some stanzas
578
578
  # (for example, if the handler raises an exception)
579
579
  # so we can't block forever.
580
- while pr > 0 and n <= 1000
580
+ while pr > 0 and n <= 20
581
581
  @tbcbmutex.synchronize { pr = @processing }
582
582
  if pr > 0
583
583
  n += 1
584
584
  Jabber::debuglog("TRYING TO CLOSE, STILL PROCESSING #{pr} STANZAS")
585
585
  #puts("TRYING TO CLOSE, STILL PROCESSING #{pr} STANZAS")
586
- Thread::pass
586
+ sleep 0.1
587
587
  end
588
588
  end
589
589
 
@@ -8,7 +8,7 @@ module Jabber
8
8
  # XMPP4R Version number. This is the ONLY place where the version number
9
9
  # should be specified. This constant is used to determine the version of
10
10
  # package tarballs and generated gems.
11
- XMPP4R_VERSION = '0.5'
11
+ XMPP4R_VERSION = '0.5.5'
12
12
  end
13
13
 
14
14
  require 'xmpp4r/client'
@@ -105,8 +105,8 @@ class SOCKS5BytestreamsTest < Test::Unit::TestCase
105
105
  target = Bytestreams::SOCKS5BytestreamsTarget.new(@server, '1', '1@a.com/1', '1@a.com/2')
106
106
 
107
107
  assert_nothing_raised do
108
- Timeout::timeout(2) do
109
- target.connect_timeout = 1
108
+ Timeout::timeout(1) do
109
+ target.connect_timeout = 0.5
110
110
  assert target.accept == false
111
111
  end
112
112
  end
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/ruby
2
+
3
+ $:.unshift File::dirname(__FILE__) + '/../../lib'
4
+
5
+ require 'test/unit'
6
+ require File::dirname(__FILE__) + '/../lib/clienttester'
7
+
8
+ require 'xmpp4r/entity_time/responder'
9
+ include Jabber
10
+
11
+ class EntityTime::ResponderTest < Test::Unit::TestCase
12
+ include ClientTester
13
+
14
+ def a_utc_time
15
+ Time.utc(2000,"jan",1,20,15,1) # xmlschema = "2000-01-01T20:15:01Z"
16
+ end
17
+
18
+ def test_entity_time_iq
19
+ el_time = EntityTime::IqTime.new(a_utc_time)
20
+
21
+ assert_equal('time', el_time.name, "element has wrong name")
22
+ assert_equal('urn:xmpp:time', el_time.namespace, "element has wrong namespace")
23
+
24
+ assert(el_time.elements["utc"], "element does not have a utc child")
25
+ assert_equal('2000-01-01T20:15:01Z', el_time.elements["utc"].text, "element/utc has the wrong text")
26
+
27
+ assert(el_time.elements["tzo"], "element does not have a tzo child")
28
+ assert_equal("+00:00", el_time.elements["tzo"].text, "element/tzo has the wrong time zone offset")
29
+ end
30
+
31
+ =begin
32
+ http://xmpp.org/extensions/xep-0202.html
33
+ <iq type='get'
34
+ from='romeo@montague.net/orchard'
35
+ to='juliet@capulet.com/balcony'
36
+ id='time_1'>
37
+ <time xmlns='urn:xmpp:time'/>
38
+ </iq>
39
+ =end
40
+ def test_query
41
+ EntityTime::Responder.new(@client)
42
+
43
+ iq = Iq.new(:get)
44
+ t = REXML::Element.new('time')
45
+ t.add_namespace('urn:xmpp:time')
46
+ iq.add_element(t)
47
+
48
+ reply = @server.send_with_id(iq)
49
+
50
+ assert_equal(:result, reply.type)
51
+ assert_equal('time', reply[0].name)
52
+ assert_equal('urn:xmpp:time', reply[0].namespace)
53
+
54
+ assert(reply[0].elements["utc"].has_text?, "element must have a utc time (actual time should be check here)")
55
+
56
+ tmp = Time.now
57
+ local_offset = Time.now.utc_offset
58
+ hour_offset = local_offset / 3600
59
+ min_offset = local_offset % 60
60
+ offset = "%+03d:%02d"%[hour_offset, min_offset]
61
+
62
+ assert_equal(offset, reply[0].elements["tzo"].text, "element should has an incorrect time zone offset")
63
+ end
64
+
65
+ end