xrbp 0.0.1

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