xmpp4r 0.5 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +21 -0
- data/README.rdoc +29 -38
- data/Rakefile +11 -18
- data/data/doc/xmpp4r/examples/advanced/recvfile.rb +5 -4
- data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +2 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +1 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +1 -1
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +4 -1
- data/lib/xmpp4r/caps/helper/generator.rb +2 -2
- data/lib/xmpp4r/client.rb +18 -4
- data/lib/xmpp4r/connection.rb +8 -5
- data/lib/xmpp4r/delay/x/delay.rb +1 -1
- data/lib/xmpp4r/entity_time.rb +6 -0
- data/lib/xmpp4r/entity_time/iq.rb +45 -0
- data/lib/xmpp4r/entity_time/responder.rb +57 -0
- data/lib/xmpp4r/httpbinding/client.rb +178 -44
- data/lib/xmpp4r/message.rb +46 -0
- data/lib/xmpp4r/muc/helper/mucclient.rb +8 -1
- data/lib/xmpp4r/observable.rb +9 -0
- data/lib/xmpp4r/observable/contact.rb +61 -0
- data/lib/xmpp4r/observable/helper.rb +389 -0
- data/lib/xmpp4r/observable/observable_thing.rb +191 -0
- data/lib/xmpp4r/observable/pubsub.rb +211 -0
- data/lib/xmpp4r/observable/subscription.rb +57 -0
- data/lib/xmpp4r/observable/thread_store.rb +65 -0
- data/lib/xmpp4r/pubsub/children/unsubscribe.rb +16 -1
- data/lib/xmpp4r/pubsub/helper/servicehelper.rb +48 -14
- data/lib/xmpp4r/rexmladdons.rb +46 -6
- data/lib/xmpp4r/roster/helper/roster.rb +19 -9
- data/lib/xmpp4r/sasl.rb +1 -1
- data/lib/xmpp4r/stream.rb +2 -2
- data/lib/xmpp4r/xmpp4r.rb +1 -1
- data/test/bytestreams/tc_socks5bytestreams.rb +2 -2
- data/test/entity_time/tc_responder.rb +65 -0
- data/test/roster/tc_helper.rb +2 -3
- data/test/tc_message.rb +52 -1
- data/test/tc_stream.rb +2 -2
- data/test/tc_streamComponent.rb +11 -1
- data/test/ts_xmpp4r.rb +11 -2
- data/xmpp4r.gemspec +20 -9
- 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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
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
|
-
|
391
|
+
res << PubSub::Subscription.import(subscription)
|
358
392
|
}
|
359
393
|
end
|
360
394
|
end
|
data/lib/xmpp4r/rexmladdons.rb
CHANGED
@@ -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
|
-
|
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.
|
232
|
+
oldpres = item.presence_of(pres.from).nil? ?
|
233
233
|
nil :
|
234
|
-
Presence.new.import(item.
|
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
|
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.
|
450
|
-
# <tt>type == :unavailable</tt>
|
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?
|
data/lib/xmpp4r/sasl.rb
CHANGED
@@ -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,
|
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}\""
|
data/lib/xmpp4r/stream.rb
CHANGED
@@ -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 <=
|
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
|
-
|
586
|
+
sleep 0.1
|
587
587
|
end
|
588
588
|
end
|
589
589
|
|
data/lib/xmpp4r/xmpp4r.rb
CHANGED
@@ -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(
|
109
|
-
target.connect_timeout =
|
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
|