xrbp 0.0.1

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +7 -0
  4. data/examples/autoconnect_timeout.rb +20 -0
  5. data/examples/cmd.rb +13 -0
  6. data/examples/ledger.rb +15 -0
  7. data/examples/ledger_multi_subscribe.rb +20 -0
  8. data/examples/ledger_subscribe.rb +18 -0
  9. data/examples/multi.rb +10 -0
  10. data/examples/paginate.rb +13 -0
  11. data/examples/prioritized.rb +13 -0
  12. data/examples/round_robin.rb +11 -0
  13. data/examples/websocket.rb +20 -0
  14. data/lib/xrbp/model/account.rb +27 -0
  15. data/lib/xrbp/model/base.rb +31 -0
  16. data/lib/xrbp/model/ledger.rb +24 -0
  17. data/lib/xrbp/model.rb +3 -0
  18. data/lib/xrbp/network/nodes.rb +8 -0
  19. data/lib/xrbp/network.rb +6 -0
  20. data/lib/xrbp/version.rb +3 -0
  21. data/lib/xrbp/websocket/client.rb +127 -0
  22. data/lib/xrbp/websocket/cmds/account_info.rb +20 -0
  23. data/lib/xrbp/websocket/cmds/account_lines.rb +33 -0
  24. data/lib/xrbp/websocket/cmds/account_objects.rb +33 -0
  25. data/lib/xrbp/websocket/cmds/account_offers.rb +33 -0
  26. data/lib/xrbp/websocket/cmds/account_tx.rb +20 -0
  27. data/lib/xrbp/websocket/cmds/book_offers.rb +38 -0
  28. data/lib/xrbp/websocket/cmds/ledger.rb +25 -0
  29. data/lib/xrbp/websocket/cmds/ledger_entry.rb +16 -0
  30. data/lib/xrbp/websocket/cmds/paginated.rb +43 -0
  31. data/lib/xrbp/websocket/cmds/server_info.rb +11 -0
  32. data/lib/xrbp/websocket/cmds/subscribe.rb +18 -0
  33. data/lib/xrbp/websocket/cmds.rb +12 -0
  34. data/lib/xrbp/websocket/command.rb +19 -0
  35. data/lib/xrbp/websocket/connection.rb +169 -0
  36. data/lib/xrbp/websocket/has_plugin.rb +30 -0
  37. data/lib/xrbp/websocket/message.rb +41 -0
  38. data/lib/xrbp/websocket/multi/fallback.rb +18 -0
  39. data/lib/xrbp/websocket/multi/multi_connection.rb +55 -0
  40. data/lib/xrbp/websocket/multi/parallel.rb +28 -0
  41. data/lib/xrbp/websocket/multi/prioritized.rb +15 -0
  42. data/lib/xrbp/websocket/multi/round_robin.rb +19 -0
  43. data/lib/xrbp/websocket/plugins/autoconnect.rb +42 -0
  44. data/lib/xrbp/websocket/plugins/command_dispatcher.rb +39 -0
  45. data/lib/xrbp/websocket/plugins/command_paginator.rb +45 -0
  46. data/lib/xrbp/websocket/plugins/connection_timeout.rb +54 -0
  47. data/lib/xrbp/websocket/plugins/message_dispatcher.rb +141 -0
  48. data/lib/xrbp/websocket/plugins/result_parser.rb +31 -0
  49. data/lib/xrbp/websocket/plugins.rb +18 -0
  50. data/lib/xrbp/websocket/socket.rb +109 -0
  51. data/lib/xrbp/websocket.rb +15 -0
  52. data/lib/xrbp.rb +5 -0
  53. metadata +123 -0
@@ -0,0 +1,43 @@
1
+ require 'active_support/core_ext/hash/except'
2
+
3
+ module XRBP
4
+ module WebSocket
5
+ module Cmds
6
+ module Paginated
7
+ attr_reader :prev_cmd
8
+
9
+ def root_cmd
10
+ return self unless prev_cmd
11
+ prev_cmd.root_cmd
12
+ end
13
+
14
+ def each_ancestor(&bl)
15
+ bl.call self
16
+ prev_cmd.each_ancestor &bl if prev_cmd
17
+ end
18
+
19
+ def parse_paginate(args)
20
+ @paginate = args[:paginate]
21
+ @prev_cmd = args[:prev_cmd]
22
+ end
23
+
24
+ def paginate_args
25
+ return :prev_cmd #, :paginate # XXX need to forward paginate
26
+ end
27
+
28
+ def args_without_paginate
29
+ args.except(*paginate_args)
30
+ end
31
+
32
+ def paginate?
33
+ !!@paginate
34
+ end
35
+
36
+ def next_page(marker)
37
+ self.class.from_h(to_h.merge({:marker => marker,
38
+ :prev_cmd => self}))
39
+ end
40
+ end # module Paginated
41
+ end # module Cmds
42
+ end # module WebSocket
43
+ end # module Wipple
@@ -0,0 +1,11 @@
1
+ module XRBP
2
+ module WebSocket
3
+ module Cmds
4
+ class ServerInfo < Command
5
+ def initialize
6
+ super({'command' => 'server_info'})
7
+ end
8
+ end
9
+ end # module Cmds
10
+ end # module WebSocket
11
+ end # module Wipple
@@ -0,0 +1,18 @@
1
+ module XRBP
2
+ module WebSocket
3
+ module Cmds
4
+ class Subscribe < Command
5
+ attr_accessor :args
6
+
7
+ def initialize(args={})
8
+ @args = args
9
+ super(to_h)
10
+ end
11
+
12
+ def to_h
13
+ args.merge(:command => :subscribe)
14
+ end
15
+ end # class Subscribe
16
+ end # module Cmds
17
+ end # module WebSocket
18
+ end # module Wipple
@@ -0,0 +1,12 @@
1
+ require_relative './cmds/paginated'
2
+ require_relative './cmds/server_info'
3
+ require_relative './cmds/ledger'
4
+ require_relative './cmds/account_lines'
5
+ require_relative './cmds/account_info'
6
+ require_relative './cmds/account_info'
7
+ require_relative './cmds/account_objects'
8
+ require_relative './cmds/account_offers'
9
+ require_relative './cmds/account_tx'
10
+ require_relative './cmds/book_offers'
11
+ require_relative './cmds/subscribe'
12
+ require_relative './cmds/ledger_entry'
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ module XRBP
4
+ module WebSocket
5
+ class Command < Message
6
+ attr_accessor :id
7
+
8
+ def initialize(data)
9
+ @@id ||= 0
10
+ @id = (@@id += 1)
11
+
12
+ json = Hash[data]
13
+ json['id'] = id
14
+
15
+ super(json.to_json)
16
+ end
17
+ end # class Command
18
+ end # module WebSocket
19
+ end # module XRBP
@@ -0,0 +1,169 @@
1
+ require_relative './has_plugin'
2
+
3
+ module XRBP
4
+ module WebSocket
5
+ class Connection
6
+ include EventEmitter
7
+ include HasPlugin
8
+
9
+ attr_reader :url
10
+ attr_accessor :parent
11
+
12
+ def initialize(url)
13
+ @url = url
14
+ @force_quit = false
15
+ end
16
+
17
+ ###
18
+
19
+ def connect
20
+ client.connect
21
+ end
22
+
23
+ def next_connection(prev)
24
+ return nil unless !!parent
25
+ parent.next_connection(prev)
26
+ end
27
+
28
+ def initialized?
29
+ !!@client
30
+ end
31
+
32
+ def open?
33
+ initialized? && client.open?
34
+ end
35
+
36
+ def closed?
37
+ !open?
38
+ end
39
+
40
+ def close!
41
+ client.close if open?
42
+ end
43
+
44
+ def send_data(data)
45
+ client.send_data(data)
46
+ end
47
+
48
+ ###
49
+
50
+ def force_quit?
51
+ @force_quit
52
+ end
53
+
54
+ def force_quit!
55
+ @force_quit = true
56
+ wake_all
57
+ end
58
+
59
+ def thread_registry
60
+ @thread_registry ||= Concurrent::Array.new
61
+ end
62
+
63
+ def rsleep(t)
64
+ thread_registry << Thread.current
65
+ sleep(t)
66
+ thread_registry.delete(Thread.current)
67
+ end
68
+
69
+ def wake_all
70
+ thread_registry.each { |th| th.wakeup }
71
+ end
72
+
73
+ ###
74
+
75
+ def wait_for_open
76
+ return unless initialized?
77
+
78
+ state_mutex.synchronize {
79
+ open_cv.wait(state_mutex, 0.1)
80
+ } until force_quit? || open?
81
+ end
82
+
83
+ def wait_for_close
84
+ return unless initialized?
85
+
86
+ state_mutex.synchronize {
87
+ close_cv.wait(state_mutex, 0.1)
88
+ } while !force_quit? && open?
89
+ end
90
+
91
+ def state_mutex
92
+ @state_mutex ||= Mutex.new
93
+ end
94
+
95
+ def open_cv
96
+ @open_cv ||= ConditionVariable.new
97
+ end
98
+
99
+ def close_cv
100
+ @close_cv ||= ConditionVariable.new
101
+ end
102
+
103
+ ###
104
+
105
+ def client
106
+ @client ||= begin
107
+ client = Client.new(@url)
108
+ conn = self
109
+
110
+ client.on :connecting do
111
+ conn.emit :connecting
112
+ conn.parent.emit :connecting if conn.parent
113
+ end
114
+
115
+ client.on :open do
116
+ conn.emit :open
117
+ conn.parent.emit :open if conn.parent
118
+
119
+ conn.state_mutex.synchronize {
120
+ conn.open_cv.signal
121
+ }
122
+
123
+ conn.plugins.each { |plg|
124
+ plg.opened if plg.respond_to?(:opened)
125
+ }
126
+ end
127
+
128
+ client.on :close do
129
+ conn.emit :close
130
+ conn.parent.emit :close if conn.parent
131
+
132
+ conn.state_mutex.synchronize {
133
+ conn.close_cv.signal
134
+ }
135
+
136
+ conn.plugins.each { |plg|
137
+ plg.closed if plg.respond_to?(:closed)
138
+ }
139
+ end
140
+
141
+ client.on :completed do |err|
142
+ conn.emit :completed
143
+ conn.parent.emit :completed if conn.parent
144
+ end
145
+
146
+ client.on :error do |err|
147
+ conn.emit :error, err
148
+ conn.parent.emit :error, err if conn.parent
149
+
150
+ conn.plugins.each { |plg|
151
+ plg.error err if plg.respond_to?(:error)
152
+ }
153
+ end
154
+
155
+ client.on :message do |msg|
156
+ conn.emit :message, msg
157
+ conn.parent.emit :message, msg if conn.parent
158
+
159
+ conn.plugins.each { |plg|
160
+ plg.message msg if plg.respond_to?(:message)
161
+ }
162
+ end
163
+
164
+ client
165
+ end
166
+ end
167
+ end # class Connection
168
+ end # module WebSocket
169
+ end # module XRBP
@@ -0,0 +1,30 @@
1
+ module XRBP
2
+ module WebSocket
3
+ module HasPlugin
4
+ def plugins
5
+ @plugins ||= []
6
+ end
7
+
8
+ def add_plugin(*plgs)
9
+ plgs.each { |plg|
10
+ plg = WebSocket.plugins[plg] if plg.is_a?(Symbol)
11
+ raise ArgumentError unless !!plg
12
+ plg = plg.new(self)
13
+ plugins << plg
14
+ plg.added if plg.respond_to?(:added)
15
+ }
16
+ end
17
+
18
+ def plugin?(plg)
19
+ plugins.collect { |plg| plg.class }.include?(plg)
20
+ end
21
+
22
+ def define_instance_method(name, &block)
23
+ (class << self; self; end).class_eval do
24
+ define_method name, &block
25
+ end
26
+ end
27
+
28
+ end # module HasPlugin
29
+ end # module WebSocket
30
+ end # module XRBP
@@ -0,0 +1,41 @@
1
+ module XRBP
2
+ module WebSocket
3
+ class Message
4
+ attr_reader :result, :time
5
+ attr_accessor :connection
6
+
7
+ def initialize(data)
8
+ @data = data
9
+ @result = nil
10
+ @cv = ConditionVariable.new
11
+ @signalled = false
12
+ @time = Time.now
13
+ end
14
+
15
+ def to_s
16
+ @data
17
+ end
18
+
19
+ def signal
20
+ @signalled = true
21
+ @cv.signal
22
+ end
23
+
24
+ def wait
25
+ connection.state_mutex.synchronize {
26
+ # only wait if we haven't received response
27
+ @cv.wait(connection.state_mutex) unless connection.closed? || @signalled
28
+ }
29
+ end
30
+
31
+ attr_writer :bl
32
+
33
+ def bl
34
+ @bl ||= proc { |res|
35
+ @result = res
36
+ signal
37
+ }
38
+ end
39
+ end # class Message
40
+ end # module WebSocket
41
+ end # module XRBP
@@ -0,0 +1,18 @@
1
+ module XRBP
2
+ module WebSocket
3
+ class Fallback < MultiConnection
4
+ def initialize(*urls)
5
+ super(*urls)
6
+ end
7
+
8
+ def next_connection(prev=nil)
9
+ unless prev.nil?
10
+ return nil if connections.last == prev
11
+ return connections[(connections.index(prev) + 1)..-1].find { |c| !c.closed? }
12
+ end
13
+
14
+ connections.find { |c| !c.closed? }
15
+ end
16
+ end # class Fallback
17
+ end # module WebSocket
18
+ end # module XRBP
@@ -0,0 +1,55 @@
1
+ require_relative '../has_plugin'
2
+
3
+ module XRBP
4
+ module WebSocket
5
+ class MultiConnection
6
+ include EventEmitter
7
+ include HasPlugin
8
+
9
+ attr_reader :connections
10
+
11
+ def initialize(*urls)
12
+ @connections = []
13
+
14
+ urls.each { |url|
15
+ @connections << Connection.new(url)
16
+ }
17
+
18
+ connections.each { |c| c.parent = self }
19
+
20
+ yield self if block_given?
21
+ end
22
+
23
+ def force_quit!
24
+ connections.each { |c| c.force_quit! }
25
+ end
26
+
27
+ def close!
28
+ connections.each { |c| c.close! }
29
+ end
30
+
31
+ alias :_add_plugin :add_plugin
32
+
33
+ def add_plugin(*plg)
34
+ connections.each { |c|
35
+ c.add_plugin *plg
36
+ }
37
+
38
+ _add_plugin(*plg)
39
+ end
40
+
41
+ # Always return first connection by default,
42
+ # override in subclasses
43
+ def next_connection(prev=nil)
44
+ return nil unless prev.nil?
45
+ @connections.first
46
+ end
47
+
48
+ def connect
49
+ @connections.each { |c|
50
+ c.connect
51
+ }
52
+ end
53
+ end # class MultiConnection
54
+ end # module WebSocket
55
+ end # module XRBP
@@ -0,0 +1,28 @@
1
+ module XRBP
2
+ module WebSocket
3
+ class Parallel < MultiConnection
4
+ class All
5
+ attr_accessor :connections
6
+
7
+ def initialize(connections)
8
+ @connections = connections
9
+ end
10
+
11
+ def method_missing(m, *args, &bl)
12
+ connections.collect { |c|
13
+ c.send(m, *args, &bl) if c.open?
14
+ }
15
+ end
16
+ end # class All
17
+
18
+ def initialize(*urls)
19
+ super(*urls)
20
+ end
21
+
22
+ def next_connection(prev=nil)
23
+ return nil unless prev.nil?
24
+ All.new connections
25
+ end
26
+ end # class RoundRobin
27
+ end # module WebSocket
28
+ end # module XRBP
@@ -0,0 +1,15 @@
1
+ module XRBP
2
+ module WebSocket
3
+ class Prioritized < MultiConnection
4
+ def initialize(*urls)
5
+ super(*urls)
6
+ end
7
+
8
+ def next_connection(prev=nil)
9
+ return nil if prev == connections.last
10
+ return super if prev.nil?
11
+ connections[connections.index(prev)+1]
12
+ end
13
+ end # class Prioritized
14
+ end # module WebSocket
15
+ end # module XRBP
@@ -0,0 +1,19 @@
1
+ module XRBP
2
+ module WebSocket
3
+ class RoundRobin < MultiConnection
4
+ def initialize(*urls)
5
+ super(*urls)
6
+ @current = 0
7
+ end
8
+
9
+ def next_connection(prev=nil)
10
+ return nil unless prev.nil?
11
+
12
+ c = connections[@current]
13
+ @current += 1
14
+ @current = 0 if @current >= connections.size
15
+ c
16
+ end
17
+ end # class RoundRobin
18
+ end # module WebSocket
19
+ end # module XRBP
@@ -0,0 +1,42 @@
1
+ module XRBP
2
+ module WebSocket
3
+ module Plugins
4
+ # Automatically reconnects on close events
5
+ class AutoConnect
6
+ attr_reader :connection
7
+
8
+ def initialize(connection)
9
+ @connection = connection
10
+ end
11
+
12
+ def added
13
+ return if connection.kind_of?(MultiConnection)
14
+
15
+ conn = connection
16
+
17
+ connection.on :completed do
18
+ connected = false
19
+ until conn.force_quit? || connected
20
+ begin
21
+ conn.connect
22
+ connected = true
23
+ rescue
24
+ conn.rsleep(3)
25
+ end
26
+ end
27
+ end
28
+
29
+ until connection.force_quit? || connection.open?
30
+ begin
31
+ connection.connect
32
+ rescue
33
+ connection.rsleep(3)
34
+ end
35
+ end
36
+ end
37
+ end # class AutoConnect
38
+
39
+ WebSocket.register_plugin :autoconnect, AutoConnect
40
+ end # module Plugins
41
+ end # module WebSocket
42
+ end # module XRBP
@@ -0,0 +1,39 @@
1
+ module XRBP
2
+ module WebSocket
3
+ module Plugins
4
+ # Dispatch commands (based on message dispatcher)
5
+ class CommandDispatcher < MessageDispatcher
6
+ def added
7
+ super
8
+
9
+ plugin = self
10
+
11
+ connection.define_instance_method(:cmd) do |cmd, &bl|
12
+ return next_connection.cmd cmd, &bl if self.kind_of?(MultiConnection)
13
+
14
+ cmd = Command.new(cmd) unless cmd.kind_of?(Command)
15
+ msg(cmd, &bl)
16
+ end
17
+ end
18
+
19
+ def match_message(msg)
20
+ begin
21
+ return nil if msg.data == ""
22
+ parsed = JSON.parse(msg.data)
23
+
24
+ rescue => e
25
+ return nil
26
+ end
27
+
28
+ id = parsed['id']
29
+ msg = messages.find { |msg| msg.kind_of?(Command) && msg.id == id }
30
+
31
+ return nil unless msg
32
+ [msg, parsed]
33
+ end
34
+ end # class CommandDispatcher
35
+
36
+ WebSocket.register_plugin :command_dispatcher, CommandDispatcher
37
+ end # module Plugins
38
+ end # module WebSocket
39
+ end # module XRBP
@@ -0,0 +1,45 @@
1
+ module XRBP
2
+ module WebSocket
3
+ module Plugins
4
+ # Handles multi-page messages
5
+ class CommandPaginator
6
+ attr_reader :connection
7
+
8
+ def initialize(connection)
9
+ @connection = connection
10
+ end
11
+
12
+ def added
13
+ raise "Must also include CommandDispatcher plugin" unless connection.plugin?(CommandDispatcher)
14
+ end
15
+
16
+ def unlock!(cmd, res)
17
+ return true unless cmd.respond_to?(:paginate?) && cmd.paginate?
18
+
19
+ marker = res["result"]["marker"]
20
+ page = res["result"][cmd.page_title]
21
+
22
+ if marker && next_cmd = cmd.next_page(marker)
23
+ connection.cmd next_cmd do
24
+ page
25
+ end
26
+
27
+ else
28
+ # XXX can't recursively use stack to unwind
29
+ # callbacks as there may be too many pages.
30
+ # Do it serially.
31
+ res = Array.new(page)
32
+ cmd.each_ancestor { |page_cmd|
33
+ page_res = page_cmd.bl.call res
34
+ res = page_res + res if page_cmd.prev_cmd
35
+ }
36
+ end
37
+
38
+ false
39
+ end
40
+ end # class CommandPaginator
41
+
42
+ WebSocket.register_plugin :command_paginator, CommandPaginator
43
+ end # module Plugins
44
+ end # module WebSocket
45
+ end # module XRBP
@@ -0,0 +1,54 @@
1
+ module XRBP
2
+ module WebSocket
3
+ module Plugins
4
+ # Automatic disconnection if no server data in certain time
5
+ class ConnectionTimeout
6
+ attr_reader :connection
7
+ attr_accessor :connection_timeout
8
+
9
+ DEFAULT_TIMEOUT = 10
10
+
11
+ def initialize(connection)
12
+ @connection = connection
13
+ @connection_timeout = DEFAULT_TIMEOUT
14
+ end
15
+
16
+ def added
17
+ plugin = self
18
+ connection.define_instance_method(:connection_timeout=) do |t|
19
+ plugin.connection_timeout = t
20
+
21
+ self.plugins.find { |plg|
22
+ plg.is_a?(ConnectionTimeout)
23
+ }.connection_timeout = t if self.kind_of?(MultiConnection)
24
+ end
25
+ end
26
+
27
+ def message(msg)
28
+ @last_msg = Time.now
29
+ end
30
+
31
+ def opened
32
+ @thread = Thread.new {
33
+ @last_msg = Time.now
34
+ until connection.force_quit? ||
35
+ connection.closed? ||
36
+ Thread.current != @thread
37
+ if Time.now - @last_msg > @connection_timeout
38
+ connection.close!
39
+ end
40
+
41
+ connection.rsleep(0.1)
42
+ end
43
+ }
44
+ end
45
+
46
+ def closed
47
+ @thread.join unless @thread == Thread.current
48
+ end
49
+ end # class ConnectionTimeout
50
+
51
+ WebSocket.register_plugin :connection_timeout, ConnectionTimeout
52
+ end # module Plugins
53
+ end # module WebSocket
54
+ end # module XRBP