cancer 0.1.0.a1

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 (89) hide show
  1. data/.autotest +3 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE.txt +22 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/cancer.gemspec +156 -0
  7. data/documentation/STREAM_INITIATION.markdown +18 -0
  8. data/examples/example.rb +80 -0
  9. data/examples/example_2.rb +20 -0
  10. data/examples/example_em.rb +11 -0
  11. data/examples/example_em_helper.rb +23 -0
  12. data/examples/example_im_roster.rb +26 -0
  13. data/examples/example_xep_0004.rb +124 -0
  14. data/examples/example_xep_0047.rb +35 -0
  15. data/examples/example_xep_0050.rb +78 -0
  16. data/examples/example_xep_0065.rb +66 -0
  17. data/examples/example_xep_0096.dup.rb +40 -0
  18. data/examples/example_xep_0096_xep_0047.rb +42 -0
  19. data/examples/example_xep_0096_xep_0065.rb +40 -0
  20. data/lib/cancer.rb +122 -0
  21. data/lib/cancer/adapter.rb +33 -0
  22. data/lib/cancer/adapters/em.rb +88 -0
  23. data/lib/cancer/adapters/socket.rb +122 -0
  24. data/lib/cancer/builder.rb +28 -0
  25. data/lib/cancer/controller.rb +32 -0
  26. data/lib/cancer/dependencies.rb +187 -0
  27. data/lib/cancer/events/abstract_event.rb +10 -0
  28. data/lib/cancer/events/base_matchers.rb +63 -0
  29. data/lib/cancer/events/binary_matchers.rb +30 -0
  30. data/lib/cancer/events/eventable.rb +92 -0
  31. data/lib/cancer/events/exception_events.rb +28 -0
  32. data/lib/cancer/events/handler.rb +33 -0
  33. data/lib/cancer/events/manager.rb +130 -0
  34. data/lib/cancer/events/named_events.rb +25 -0
  35. data/lib/cancer/events/proc_matcher.rb +16 -0
  36. data/lib/cancer/events/xml_events.rb +44 -0
  37. data/lib/cancer/exceptions.rb +53 -0
  38. data/lib/cancer/jid.rb +215 -0
  39. data/lib/cancer/lock.rb +32 -0
  40. data/lib/cancer/spec.rb +35 -0
  41. data/lib/cancer/spec/error.rb +18 -0
  42. data/lib/cancer/spec/extentions.rb +79 -0
  43. data/lib/cancer/spec/matcher.rb +30 -0
  44. data/lib/cancer/spec/mock_adapter.rb +139 -0
  45. data/lib/cancer/spec/mock_stream.rb +15 -0
  46. data/lib/cancer/spec/transcript.rb +107 -0
  47. data/lib/cancer/stream.rb +182 -0
  48. data/lib/cancer/stream/builder.rb +20 -0
  49. data/lib/cancer/stream/controller.rb +36 -0
  50. data/lib/cancer/stream/event_helper.rb +12 -0
  51. data/lib/cancer/stream/xep.rb +52 -0
  52. data/lib/cancer/stream_parser.rb +144 -0
  53. data/lib/cancer/support.rb +27 -0
  54. data/lib/cancer/support/hash.rb +32 -0
  55. data/lib/cancer/support/string.rb +22 -0
  56. data/lib/cancer/synchronized_stanza.rb +79 -0
  57. data/lib/cancer/thread_pool.rb +118 -0
  58. data/lib/cancer/xep.rb +43 -0
  59. data/lib/cancer/xeps/core.rb +109 -0
  60. data/lib/cancer/xeps/core/bind.rb +33 -0
  61. data/lib/cancer/xeps/core/sasl.rb +113 -0
  62. data/lib/cancer/xeps/core/session.rb +22 -0
  63. data/lib/cancer/xeps/core/stream.rb +18 -0
  64. data/lib/cancer/xeps/core/terminator.rb +21 -0
  65. data/lib/cancer/xeps/core/tls.rb +34 -0
  66. data/lib/cancer/xeps/im.rb +323 -0
  67. data/lib/cancer/xeps/xep_0004_x_data.rb +692 -0
  68. data/lib/cancer/xeps/xep_0020_feature_neg.rb +35 -0
  69. data/lib/cancer/xeps/xep_0030_disco.rb +167 -0
  70. data/lib/cancer/xeps/xep_0047_ibb.rb +322 -0
  71. data/lib/cancer/xeps/xep_0050_commands.rb +256 -0
  72. data/lib/cancer/xeps/xep_0065_bytestreams.rb +306 -0
  73. data/lib/cancer/xeps/xep_0066_oob.rb +69 -0
  74. data/lib/cancer/xeps/xep_0095_si.rb +211 -0
  75. data/lib/cancer/xeps/xep_0096_si_filetransfer.rb +173 -0
  76. data/lib/cancer/xeps/xep_0114_component.rb +73 -0
  77. data/lib/cancer/xeps/xep_0115_caps.rb +180 -0
  78. data/lib/cancer/xeps/xep_0138_compress.rb +134 -0
  79. data/lib/cancer/xeps/xep_0144_rosterx.rb +250 -0
  80. data/lib/cancer/xeps/xep_0184_receipts.rb +40 -0
  81. data/lib/cancer/xeps/xep_0199_ping.rb +41 -0
  82. data/spec/spec.opts +2 -0
  83. data/spec/spec_helper.rb +14 -0
  84. data/spec/stream/stanza_errors_spec.rb +47 -0
  85. data/spec/stream/stream_errors_spec.rb +38 -0
  86. data/spec/stream/stream_initialization_spec.rb +160 -0
  87. data/spec/xep_0050/local_spec.rb +165 -0
  88. data/spec/xep_0050/remote_spec.rb +44 -0
  89. metadata +200 -0
@@ -0,0 +1,36 @@
1
+
2
+ module Cancer
3
+ class Stream
4
+ module Controller
5
+
6
+ def controllers
7
+ @controllers ||= []
8
+ end
9
+
10
+ def controllers_by_name
11
+ @controllers_by_name ||= {}
12
+ end
13
+
14
+ def install_controller(klass)
15
+ self.controllers.push(klass)
16
+ self.controllers_by_name[klass.to_s] = klass.new(self)
17
+ self.controllers_by_name[klass.to_s].register_with(@manager)
18
+ @manager.resolve_martchers! if @initialized
19
+ end
20
+
21
+ def extend_controller(klass, &proc)
22
+ object = self.controllers_by_name[klass.to_s]
23
+ raise "Controller not found #{klass}" if object.nil?
24
+ metaklass = (class << object ; self ; end)
25
+ metaklass.instance_eval(&proc)
26
+ end
27
+
28
+ def controller(&proc)
29
+ controller = Class.new(Cancer::Controller)
30
+ controller.class_eval(&proc)
31
+ install_controller controller
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module Cancer
3
+ class Stream
4
+ module EventHelper
5
+
6
+ def install_event_helper(mod)
7
+ Cancer::Events::Manager::DSL.send :include, mod
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+
2
+ module Cancer
3
+ class Stream
4
+ module XEP
5
+
6
+ def xeps
7
+ @xeps ||= []
8
+ end
9
+
10
+ def install_xep(name)
11
+ if @initialized
12
+ unless self.xeps.include?(name)
13
+ xep = Cancer::XEP.const_for(name)
14
+ xep.strong_dependecies.each do |dep|
15
+ install_xep(dep)
16
+ end
17
+ xep.enhance_stream(self)
18
+ end
19
+ else
20
+ self.xeps.push(name)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def enhance_stream_with_xeps!
27
+ xeps_by_name = self.xeps.inject({}) do |hash, xep_name|
28
+ hash[xep_name] = Cancer::XEP.const_for(xep_name)
29
+ hash
30
+ end
31
+
32
+ resolver = Cancer::Dependencies::Resolver.new(:allow_reverse => false)
33
+ resolver.on_missing_strong_dependency do |xep_name|
34
+ xep = Cancer::XEP.const_for(xep_name)
35
+ xeps_by_name[xep_name] = xep
36
+ resolver.add_node(xep_name, xep)
37
+ install_xep(xep_name)
38
+ end
39
+ xeps_by_name.each do |xep_name, xep|
40
+ resolver.add_node(xep_name, xep)
41
+ end
42
+ orderd_xeps = resolver.sort(*self.xeps)
43
+ Cancer.logger.debug "XEPS: #{orderd_xeps.inspect}"
44
+ orderd_xeps.each do |xep_name|
45
+ xep = xeps_by_name[xep_name]
46
+ xep.enhance_stream(self)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,144 @@
1
+
2
+ module Cancer
3
+ class StreamParser < Nokogiri::XML::SAX::Document
4
+
5
+ def initialize(stream)
6
+ @stream = stream
7
+ @document = nil
8
+ @current = nil
9
+ @current_ns = nil
10
+ end
11
+
12
+ ###
13
+ # Called when document starts parsing
14
+ def start_document
15
+ end
16
+
17
+ ###
18
+ # Called when document ends parsing
19
+ def end_document
20
+ end
21
+
22
+ ###
23
+ # Called at the beginning of an element
24
+ # +name+ is the element name
25
+ # +attrs+ is a list of attributes
26
+ # +prefix+ is the namespace prefix for the element
27
+ # +uri+ is the associated namespace URI
28
+ # +ns+ is a hash of namespace prefix:urls associated with the element
29
+ def start_element_namespace(name, attrs=nil, prefix=nil, uri=nil, ns=nil)
30
+ @document = (@current ? @document : Nokogiri::XML::Document.new)
31
+ @current_ns = (@current ? @current_ns : {})
32
+
33
+ element = Nokogiri::XML::Element.new(name, @document)
34
+
35
+ if @current
36
+ @current.add_child element if @current
37
+ else
38
+ @document.root = element
39
+ end
40
+
41
+ if ns
42
+ ns.each do |p, u|
43
+ @current_ns[u] = element.add_namespace(p, u)
44
+ end
45
+ end
46
+
47
+ if uri
48
+ element.namespace = @current_ns[uri] || element.add_namespace(prefix, uri)
49
+ end
50
+
51
+ if attrs
52
+ attrs.each do |attribute|
53
+ element[attribute.localname] = attribute.value
54
+ end
55
+ end
56
+
57
+ if element.named?('stream', STREAM_NS)
58
+ @stream.received_xml(element)
59
+ @current = nil
60
+ else
61
+ @current = element
62
+ end
63
+ end
64
+
65
+ ###
66
+ # Called at the end of an element
67
+ # +name+ is the element's name
68
+ # +prefix+ is the namespace prefix associated with the element
69
+ # +uri+ is the associated namespace URI
70
+ def end_element_namespace name, prefix = nil, uri = nil
71
+ if name == 'stream' and prefix == 'stream' and @current == nil
72
+ else
73
+ if Nokogiri::XML::Document === @current.parent or @current.parent.nil?
74
+ @stream.received_xml(@current)
75
+ @current = nil
76
+ else
77
+ @current = @current.parent
78
+ end
79
+ end
80
+ end
81
+
82
+ ###
83
+ # Characters read between a tag
84
+ # +string+ contains the character data
85
+ def characters string
86
+ @current << Nokogiri::XML::Text.new(string, @document) if @current
87
+ end
88
+
89
+ ###
90
+ # Called when cdata blocks are found
91
+ # +string+ contains the cdata content
92
+ def cdata_block string
93
+ @current << Nokogiri::XML::CDATA.new(@document, string) if @current
94
+ end
95
+
96
+ ###
97
+ # Called on document warnings
98
+ # +string+ contains the warning
99
+ def warning string
100
+ end
101
+
102
+ ###
103
+ # Called on document errors
104
+ # +string+ contains the error
105
+ def error string
106
+ end
107
+
108
+ end
109
+ end
110
+
111
+ class Nokogiri::XML::Node
112
+ def named?(name, ns=nil)
113
+ self.name == name and (ns.nil? or (namespace and namespace.href == ns))
114
+ end
115
+
116
+ def first(xpath, ns={})
117
+ ns = add_xmpp_base_namespaces_to(ns)
118
+ element = xpath(xpath, ns).to_a.first
119
+ if element and block_given?
120
+ yield(element)
121
+ else
122
+ element
123
+ end
124
+ end
125
+
126
+ def all(xpath, ns={}, &block)
127
+ ns = add_xmpp_base_namespaces_to(ns)
128
+ elements = xpath(xpath, ns)
129
+ if block_given?
130
+ elements.each(&block)
131
+ else
132
+ elements
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def add_xmpp_base_namespaces_to(ns)
139
+ { 'stream' => Cancer::STREAM_NS,
140
+ 'client' => Cancer::CLIENT_NS,
141
+ 'c' => Cancer::CLIENT_NS }.merge(ns)
142
+ end
143
+
144
+ end
@@ -0,0 +1,27 @@
1
+
2
+ module Cancer
3
+ module Support
4
+
5
+ def alias_method_chain(target, feature)
6
+ # Strip out punctuation on predicates or bang methods since
7
+ # e.g. target?_without_feature is not a valid method name.
8
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
9
+ yield(aliased_target, punctuation) if block_given?
10
+
11
+ with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
12
+
13
+ alias_method without_method, target
14
+ alias_method target, with_method
15
+
16
+ case
17
+ when public_method_defined?(without_method)
18
+ public target
19
+ when protected_method_defined?(without_method)
20
+ protected target
21
+ when private_method_defined?(without_method)
22
+ private target
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+
2
+ module Cancer
3
+ module Support
4
+ module Hash
5
+
6
+ def clean!
7
+ delete_if { |key, value| value.nil? }
8
+ end
9
+
10
+ def clean
11
+ dup.clean!
12
+ end
13
+
14
+ def to_options
15
+ self.inject({}) do |options, (key, value)|
16
+ options[key.to_sym] = value
17
+ options
18
+ end
19
+ end
20
+
21
+ def to_options!
22
+ self.keys.each do |key|
23
+ self[key.to_sym] = self.delete(key)
24
+ end
25
+ self
26
+ end
27
+
28
+ end
29
+
30
+ ::Hash.send :include, Cancer::Support::Hash
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+
2
+ module Cancer
3
+ module Support
4
+ module String
5
+
6
+ def cmp_ioctet(other)
7
+ i = 0
8
+ loop do
9
+ return 0 if (self.size - i) == 0 and (other.size - i) == 0
10
+ return -1 if (self.size - i) == 0
11
+ return 1 if (other.size - i) == 0
12
+ c = self[i] <=> other[i]
13
+ return c if c != 0
14
+ i += 1
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ ::String.send :include, Cancer::Support::String
21
+ end
22
+ end
@@ -0,0 +1,79 @@
1
+
2
+ module Cancer
3
+ module SynchronizedStanza
4
+
5
+ def send_and_receive(stanza, *conditions, &proc)
6
+ lock = nil
7
+ @mutex.synchronize do
8
+ conditions.push(proc) if proc
9
+ lock = Cancer::SynchronizedStanza::Lock.new(*conditions)
10
+ @receive_locks.push(lock)
11
+ send(stanza)
12
+ end
13
+ r = lock.wait!
14
+ @receive_locks.delete(lock)
15
+ if r[:type] == 'error'
16
+ handle_stanza_errors(r)
17
+ else
18
+ return r
19
+ end
20
+ ensure
21
+ @receive_locks.delete(lock)
22
+ end
23
+
24
+ def handle_stanza_errors(xml)
25
+ raise Cancer::StanzaError.build(xml)
26
+ end
27
+
28
+ def handle_synchronized_stanza(xml)
29
+ @mutex.synchronize do
30
+ @receive_locks.each do |lock|
31
+ return true if lock.continue! xml
32
+ end
33
+ end
34
+ (Exception === xml)
35
+ end
36
+
37
+ class Lock
38
+ def initialize(*conditions)
39
+ @mutex = Mutex.new
40
+ @condition = ConditionVariable.new
41
+ @conditions = conditions
42
+ @conditions.collect! do |condition|
43
+ case condition
44
+ when String then lambda { |xml| !!xml.first(condition) }
45
+ when Array then lambda { |xml| !!xml.first(*condition) }
46
+ when Proc then condition
47
+ end
48
+ end
49
+ @conditions.push(lambda { |xml| true })
50
+ end
51
+
52
+ def wait!
53
+ raise @exception if @exception
54
+ return @xml if @xml
55
+ @mutex.synchronize do
56
+ @condition.wait(@mutex)
57
+ raise @exception if @exception
58
+ return @xml
59
+ end
60
+ end
61
+
62
+ def continue!(xml)
63
+ @mutex.synchronize do
64
+ if Exception === xml
65
+ @exception = xml
66
+ @condition.signal
67
+ return false
68
+ end
69
+ unless @conditions.any? { |condition| FalseClass === condition.call(xml) }
70
+ @xml = xml
71
+ @condition.signal
72
+ return true
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,118 @@
1
+
2
+ require 'thread'
3
+ require 'monitor'
4
+
5
+ module Cancer
6
+ class ThreadPool < Queue
7
+
8
+ # create a new pool and start it as well.
9
+ def self.start!(size=10, &proc)
10
+ new(size, &proc).start!
11
+ end
12
+
13
+ # Build a new thread pool.
14
+ # @param [Fixnum] size the size of the pool
15
+ # @yield [job] The code to run in for each job on the queue
16
+ # @yieldparam [Object] job the job which needs to be processed.
17
+ def initialize(size=10, &proc)
18
+ extend MonitorMixin
19
+ @proc = proc
20
+ @empty_cond = new_cond
21
+ @pool = []
22
+ @defered_pool = []
23
+ @size = size
24
+ @num_working = 0
25
+ @exception_handler_proc = nil
26
+ super()
27
+ end
28
+
29
+ # set a handler for exceptions raised inside the pool threads
30
+ # @yield [exception] handle exceptions in here
31
+ # @yieldparam [Exception] exceptions the exception that was raised in side a pool thread.
32
+ def on_exception(&proc)
33
+ @exception_handler_proc = proc
34
+ end
35
+
36
+ # push a new job onto the queue.
37
+ def push(*args)
38
+ super(args)
39
+ end
40
+
41
+ def rescue_any_exceptions
42
+ defult_handler = lambda do |e|
43
+ Cancer.logger.fatal e
44
+ end
45
+
46
+ begin
47
+ yield
48
+ rescue Exception => e
49
+ begin
50
+ (@exception_handler_proc || defult_handler).call(e)
51
+ rescue Exception => e
52
+ defult_handler.call(e)
53
+ end
54
+ end
55
+ end
56
+
57
+ # create the threads and start processing jobs.
58
+ def start!
59
+ @size.times do
60
+ @pool.push(Thread.new(self) do |queue|
61
+ Thread.current.abort_on_exception = true
62
+
63
+ loop do
64
+ rescue_any_exceptions do
65
+ job = queue.pop
66
+ queue.synchronize { @num_working += 1 }
67
+ @proc.call(*job)
68
+ end
69
+ queue.synchronize do
70
+ @num_working -= 1
71
+ @empty_cond.signal
72
+ end
73
+ end
74
+
75
+ end)
76
+ end
77
+ self
78
+ end
79
+
80
+ # wait until the queue is empty.
81
+ def wait!
82
+ synchronize do
83
+ @empty_cond.wait_until do
84
+ ((empty?) and
85
+ (@num_working == 0) and
86
+ (@defered_pool.empty?))
87
+ end
88
+ end
89
+ end
90
+
91
+ # wait until the queue is empty and then stop the pool.
92
+ def stop!
93
+ wait!
94
+ @pool.each { |thread| thread.kill }
95
+ @pool = []
96
+ self
97
+ end
98
+
99
+ # start a thread outside the thread pool.
100
+ # @param args the arguments to pass to the thread
101
+ # @yield [*args] the block to execute inside the new thread.
102
+ def defer(*args, &proc)
103
+ @defered_pool.push(Thread.new(self, proc, args) do |queue, proc, args|
104
+ Thread.current.abort_on_exception = true
105
+
106
+ rescue_any_exceptions do
107
+ proc.call(*args)
108
+ end
109
+ queue.synchronize do
110
+ @defered_pool.delete(Thread.current)
111
+ @empty_cond.signal
112
+ end
113
+
114
+ end)
115
+ end
116
+
117
+ end
118
+ end