xmpp4r 0.5 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
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