xrbp 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +103 -5
- data/examples/accounts.rb +13 -0
- data/examples/autoconnect_timeout.rb +5 -1
- data/examples/autorety.rb +7 -0
- data/examples/crawl_nodes.rb +32 -0
- data/examples/dsl/account.rb +16 -0
- data/examples/dsl/ledger.rb +7 -0
- data/examples/dsl/ledger_subscribe.rb +18 -0
- data/examples/dsl/validators.rb +8 -0
- data/examples/gateways.rb +8 -0
- data/examples/latest_account.rb +4 -0
- data/examples/ledger_multi_subscribe.rb +1 -1
- data/examples/ledger_subscribe.rb +2 -2
- data/examples/market.rb +13 -0
- data/examples/username.rb +12 -0
- data/examples/validator.rb +8 -0
- data/lib/xrbp.rb +5 -1
- data/lib/xrbp/common.rb +10 -0
- data/lib/xrbp/core_ext.rb +24 -0
- data/lib/xrbp/dsl.rb +25 -0
- data/lib/xrbp/dsl/accounts.rb +13 -0
- data/lib/xrbp/dsl/ledgers.rb +23 -0
- data/lib/xrbp/dsl/validators.rb +10 -0
- data/lib/xrbp/dsl/webclient.rb +8 -0
- data/lib/xrbp/dsl/websocket.rb +28 -0
- data/lib/xrbp/model.rb +4 -0
- data/lib/xrbp/model/account.rb +142 -1
- data/lib/xrbp/model/base.rb +1 -0
- data/lib/xrbp/model/gateway.rb +24 -0
- data/lib/xrbp/model/ledger.rb +30 -1
- data/lib/xrbp/model/market.rb +52 -0
- data/lib/xrbp/model/node.rb +131 -0
- data/lib/xrbp/model/parsers/account.rb +44 -0
- data/lib/xrbp/model/parsers/gateway.rb +40 -0
- data/lib/xrbp/model/parsers/market.rb +28 -0
- data/lib/xrbp/model/parsers/node.rb +19 -0
- data/lib/xrbp/model/parsers/quote.rb +47 -0
- data/lib/xrbp/model/parsers/validator.rb +25 -0
- data/lib/xrbp/model/validator.rb +24 -0
- data/lib/xrbp/plugins.rb +6 -0
- data/lib/xrbp/plugins/base.rb +10 -0
- data/lib/xrbp/plugins/has_plugin.rb +45 -0
- data/lib/xrbp/plugins/has_result_parsers.rb +27 -0
- data/lib/xrbp/plugins/plugin_registry.rb +20 -0
- data/lib/xrbp/plugins/result_parser.rb +19 -0
- data/lib/xrbp/terminatable.rb +19 -0
- data/lib/xrbp/thread_registry.rb +22 -0
- data/lib/xrbp/version.rb +1 -1
- data/lib/xrbp/webclient.rb +2 -0
- data/lib/xrbp/webclient/connection.rb +100 -0
- data/lib/xrbp/webclient/plugins.rb +8 -0
- data/lib/xrbp/webclient/plugins/autoretry.rb +54 -0
- data/lib/xrbp/webclient/plugins/result_parser.rb +23 -0
- data/lib/xrbp/websocket/client.rb +85 -24
- data/lib/xrbp/websocket/cmds/account_info.rb +4 -0
- data/lib/xrbp/websocket/cmds/account_lines.rb +5 -0
- data/lib/xrbp/websocket/cmds/account_objects.rb +4 -0
- data/lib/xrbp/websocket/cmds/account_offers.rb +5 -0
- data/lib/xrbp/websocket/cmds/account_tx.rb +4 -0
- data/lib/xrbp/websocket/cmds/book_offers.rb +4 -0
- data/lib/xrbp/websocket/cmds/ledger.rb +3 -0
- data/lib/xrbp/websocket/cmds/ledger_entry.rb +4 -0
- data/lib/xrbp/websocket/cmds/paginated.rb +4 -1
- data/lib/xrbp/websocket/cmds/server_info.rb +4 -0
- data/lib/xrbp/websocket/cmds/subscribe.rb +4 -0
- data/lib/xrbp/websocket/command.rb +11 -0
- data/lib/xrbp/websocket/connection.rb +75 -22
- data/lib/xrbp/websocket/message.rb +5 -2
- data/lib/xrbp/websocket/multi/fallback.rb +2 -4
- data/lib/xrbp/websocket/multi/multi_connection.rb +37 -2
- data/lib/xrbp/websocket/multi/parallel.rb +2 -4
- data/lib/xrbp/websocket/multi/prioritized.rb +2 -4
- data/lib/xrbp/websocket/multi/round_robin.rb +4 -0
- data/lib/xrbp/websocket/plugins.rb +1 -7
- data/lib/xrbp/websocket/plugins/autoconnect.rb +25 -5
- data/lib/xrbp/websocket/plugins/command_dispatcher.rb +5 -0
- data/lib/xrbp/websocket/plugins/command_paginator.rb +9 -8
- data/lib/xrbp/websocket/plugins/connection_timeout.rb +27 -16
- data/lib/xrbp/websocket/plugins/message_dispatcher.rb +60 -30
- data/lib/xrbp/websocket/plugins/result_parser.rb +19 -19
- data/lib/xrbp/websocket/socket.rb +23 -6
- metadata +118 -8
- data/lib/xrbp/network.rb +0 -6
- data/lib/xrbp/network/nodes.rb +0 -8
- data/lib/xrbp/websocket/has_plugin.rb +0 -30
@@ -0,0 +1,28 @@
|
|
1
|
+
module XRBP
|
2
|
+
module DSL
|
3
|
+
# Default websocket endpoints. Override to specify
|
4
|
+
# different ones.
|
5
|
+
def websocket_endpoints
|
6
|
+
["wss://s1.ripple.com:443", "wss://s2.ripple.com:443"]
|
7
|
+
end
|
8
|
+
|
9
|
+
# Client which may be used to access websocket endpoints.
|
10
|
+
#
|
11
|
+
# By default a RoundRobin strategy will be used to cycle
|
12
|
+
# through specified endpoints.
|
13
|
+
def websocket
|
14
|
+
@websocket ||= WebSocket::RoundRobin.new *websocket_endpoints
|
15
|
+
end
|
16
|
+
|
17
|
+
# Register a callback to be invoked when messages are received
|
18
|
+
# via websocket connections
|
19
|
+
def websocket_msg(&bl)
|
20
|
+
websocket.on :message, &bl
|
21
|
+
end
|
22
|
+
|
23
|
+
# Block until all websocket connections are closed
|
24
|
+
def websocket_wait
|
25
|
+
websocket.wait_for_close
|
26
|
+
end
|
27
|
+
end # module DSL
|
28
|
+
end # module XRBP
|
data/lib/xrbp/model.rb
CHANGED
data/lib/xrbp/model/account.rb
CHANGED
@@ -1,27 +1,168 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require_relative './parsers/account'
|
3
|
+
|
1
4
|
module XRBP
|
2
5
|
module Model
|
3
6
|
class Account < Base
|
4
7
|
extend Base::ClassMethods
|
5
8
|
|
9
|
+
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
10
|
+
|
6
11
|
attr_accessor :id
|
7
12
|
|
8
|
-
|
13
|
+
# Local data cache location
|
14
|
+
def self.cache
|
15
|
+
@cache ||= File.expand_path("~/.xrbp/accounts/")
|
16
|
+
end
|
17
|
+
|
18
|
+
# All cached accounts
|
19
|
+
def self.cached
|
20
|
+
Dir.glob("#{cache}/*").collect { |f|
|
21
|
+
next nil if f == "#{cache}marker" ||
|
22
|
+
f == "#{cache}start"
|
23
|
+
begin
|
24
|
+
JSON.parse(File.read(f))
|
25
|
+
rescue
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
}.flatten.compact
|
9
29
|
end
|
10
30
|
|
31
|
+
# TODO invoke gen account command via websocket
|
32
|
+
#def self.create(opts={})
|
33
|
+
#end
|
34
|
+
|
35
|
+
# TODO 'parallel accounts' method,
|
36
|
+
# - split period between Genesis and Time.now into
|
37
|
+
# N-segments (of configurable length: hours, days,
|
38
|
+
# months, etc)
|
39
|
+
# - parallel retrieve segments specifying start param
|
40
|
+
# & following markers until data is no longer
|
41
|
+
# in-domain
|
42
|
+
# - emit :account signal w/ each account during process
|
43
|
+
# & reassemble complete set after
|
44
|
+
|
45
|
+
# Retrieve all accounts via WebClient::Connection
|
46
|
+
#
|
47
|
+
# @param opts [Hash] options to retrieve accounts with
|
48
|
+
# @option opts [WebClient::Connection] :connection Connection
|
49
|
+
# to use to retrieve accounts
|
50
|
+
def self.all(opts={})
|
51
|
+
set_opts(opts)
|
52
|
+
FileUtils.mkdir_p(cache) unless File.exist?(cache)
|
53
|
+
|
54
|
+
# start at last marker
|
55
|
+
marker = File.exist?("#{cache}/marker") ?
|
56
|
+
File.read("#{cache}/marker") : nil
|
57
|
+
|
58
|
+
# load start time, if set
|
59
|
+
start = File.exist?("#{cache}/start") ?
|
60
|
+
File.read("#{cache}/start") :
|
61
|
+
GENESIS_TIME.strftime(DATE_FORMAT)
|
62
|
+
|
63
|
+
# Parse results
|
64
|
+
connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
|
65
|
+
connection.add_plugin Parsers::AccountInfo unless connection.plugin?(Parsers::AccountInfo)
|
66
|
+
|
67
|
+
# Retrieve data until complete
|
68
|
+
accounts = []
|
69
|
+
finished = false
|
70
|
+
until finished || connection.force_quit?
|
71
|
+
# HTTP request
|
72
|
+
connection.url = "https://data.ripple.com/v2/accounts/?"\
|
73
|
+
"start=#{start}&limit=1000&marker=#{marker}"
|
74
|
+
res = connection.perform
|
75
|
+
|
76
|
+
# Cache data
|
77
|
+
cache_file = "#{cache}/#{marker || "genesis"}"
|
78
|
+
File.write(cache_file, res[:accounts].to_json)
|
79
|
+
|
80
|
+
# Emit signal
|
81
|
+
res[:accounts].each { |acct|
|
82
|
+
break if connection.force_quit?
|
83
|
+
connection.emit :account, acct
|
84
|
+
}
|
85
|
+
|
86
|
+
break if connection.force_quit?
|
87
|
+
|
88
|
+
marker = res[:marker]
|
89
|
+
accounts += res[:accounts]
|
90
|
+
|
91
|
+
# Store state, eval exit condition
|
92
|
+
File.write("#{cache}/marker", marker.to_s)
|
93
|
+
finished = !marker
|
94
|
+
end
|
95
|
+
|
96
|
+
# Store state for next run
|
97
|
+
File.write("#{cache}/start",
|
98
|
+
accounts.last[:inception]) unless marker
|
99
|
+
|
100
|
+
accounts
|
101
|
+
end
|
102
|
+
|
103
|
+
# Retrieve latest account using specified WebClient::Connection
|
104
|
+
#
|
105
|
+
# @param opts [Hash] options to retrieve account with
|
106
|
+
# @option opts [WebClient::Connection] :connection Connection
|
107
|
+
# to use to retrieve account
|
108
|
+
def self.latest(opts={})
|
109
|
+
set_opts(opts)
|
110
|
+
|
111
|
+
connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
|
112
|
+
connection.add_plugin Parsers::AccountInfo unless connection.plugin?(Parsers::AccountInfo)
|
113
|
+
|
114
|
+
connection.url = "https://data.ripple.com/v2/accounts/?"\
|
115
|
+
"descending=true&limit=1000"
|
116
|
+
res = connection.perform
|
117
|
+
res[:accounts].first
|
118
|
+
end
|
119
|
+
|
120
|
+
###
|
121
|
+
|
122
|
+
# Initialize new Account instance
|
123
|
+
#
|
124
|
+
# @param opts [Hash] options to initialize account with
|
125
|
+
# @option opts [String] :id id of account
|
126
|
+
# to use to retrieve account
|
11
127
|
def initialize(opts={})
|
12
128
|
@id = opts[:id]
|
13
129
|
super(opts)
|
14
130
|
end
|
15
131
|
|
132
|
+
# Retrieve account info via WebSocket::Connection
|
133
|
+
#
|
134
|
+
# @param opts [Hash] options to retrieve account info with
|
135
|
+
# @option opts [WebSocket::Connection] :connection Connection
|
136
|
+
# to use to retrieve account info
|
16
137
|
def info(opts={}, &bl)
|
17
138
|
set_opts(opts)
|
18
139
|
connection.cmd(WebSocket::Cmds::AccountInfo.new(id, full_opts), &bl)
|
19
140
|
end
|
20
141
|
|
142
|
+
# Retrieve account objects via WebSocket::Connection
|
143
|
+
#
|
144
|
+
# @param opts [Hash] options to retrieve account objects with
|
145
|
+
# @option opts [WebSocket::Connection] :connection Connection
|
146
|
+
# to use to retrieve account objects
|
21
147
|
def objects(opts={}, &bl)
|
22
148
|
set_opts(opts)
|
23
149
|
connection.cmd(WebSocket::Cmds::AccountObjects.new(id, full_opts), &bl)
|
24
150
|
end
|
151
|
+
|
152
|
+
# Retrieve account username via WebClient::Connection
|
153
|
+
#
|
154
|
+
# @param opts [Hash] options to retrieve account username with
|
155
|
+
# @option opts [WebClient::Connection] :connection Connection
|
156
|
+
# to use to retrieve account username
|
157
|
+
def username(opts={}, &bl)
|
158
|
+
set_opts(opts)
|
159
|
+
connection.url = "https://id.ripple.com/v1/authinfo?username=#{id}"
|
160
|
+
|
161
|
+
connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
|
162
|
+
connection.add_plugin Parsers::AccountUsername unless connection.plugin?(Parsers::AccountUsername)
|
163
|
+
|
164
|
+
connection.perform
|
165
|
+
end
|
25
166
|
end # class Account
|
26
167
|
end # module Model
|
27
168
|
end # module XRBP
|
data/lib/xrbp/model/base.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative './parsers/gateway'
|
2
|
+
|
3
|
+
module XRBP
|
4
|
+
module Model
|
5
|
+
class Gateway < Base
|
6
|
+
extend Base::ClassMethods
|
7
|
+
|
8
|
+
# Retrieve list of gateways provided WebClient::Connection.
|
9
|
+
#
|
10
|
+
# @param opts [Hash] options to retrieve gateway list with
|
11
|
+
# @option opts [WebClient::Connection] :connection Connection
|
12
|
+
# to use to retrieve gateway list
|
13
|
+
def self.all(opts={})
|
14
|
+
set_opts(opts)
|
15
|
+
connection.url = "https://data.ripple.com/v2/gateways"
|
16
|
+
|
17
|
+
connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
|
18
|
+
connection.add_plugin Parsers::Gateway unless connection.plugin?(Parsers::Gateway)
|
19
|
+
|
20
|
+
connection.perform
|
21
|
+
end
|
22
|
+
end # class Gateway
|
23
|
+
end # module Model
|
24
|
+
end # module XRBP
|
data/lib/xrbp/model/ledger.rb
CHANGED
@@ -10,14 +10,43 @@ module XRBP
|
|
10
10
|
super(opts)
|
11
11
|
end
|
12
12
|
|
13
|
+
# Retreive specified ledger via WebSocket::Connection
|
14
|
+
#
|
15
|
+
# @param opts [Hash] options to retrieve ledger with
|
16
|
+
# @option opts [WebSocket::Connection] :connection Connection
|
17
|
+
# to use to retrieve ledger
|
13
18
|
def sync(opts={}, &bl)
|
14
19
|
set_opts(opts)
|
15
20
|
connection.cmd(WebSocket::Cmds::Ledger.new(id, full_opts), &bl)
|
16
21
|
end
|
17
22
|
|
23
|
+
# Subscribe to ledger stream via WebSocket::Connection
|
24
|
+
#
|
25
|
+
# @param opts [Hash] options to subscribe to ledger stream with
|
26
|
+
# @option opts [WebSocket::Connection] :connection Connection
|
27
|
+
# to use to subscribe to ledger stream
|
18
28
|
def self.subscribe(opts={}, &bl)
|
19
29
|
set_opts(opts)
|
20
|
-
|
30
|
+
conn = connection
|
31
|
+
conn.cmd(WebSocket::Cmds::Subscribe.new(:streams => ["ledger"]), &bl)
|
32
|
+
conn.on :message do |*args|
|
33
|
+
c,msg = args.size > 1 ? [args[0], args[1]] :
|
34
|
+
[nil, args[0]]
|
35
|
+
|
36
|
+
begin
|
37
|
+
i = JSON.parse(msg.to_s)
|
38
|
+
if i["ledger_hash"] &&
|
39
|
+
i["ledger_index"]
|
40
|
+
if c
|
41
|
+
conn.emit :ledger, c, i
|
42
|
+
else
|
43
|
+
conn.emit :ledger, i
|
44
|
+
conn.parent.emit :ledger, conn, i if conn.parent
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rescue
|
48
|
+
end
|
49
|
+
end
|
21
50
|
end
|
22
51
|
end # class Ledger
|
23
52
|
end # module Model
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative './parsers/market'
|
2
|
+
require_relative './parsers/quote'
|
3
|
+
|
4
|
+
module XRBP
|
5
|
+
module Model
|
6
|
+
class Market < Base
|
7
|
+
extend Base::ClassMethods
|
8
|
+
# TODO plugabble system to pull in markets from other sources
|
9
|
+
|
10
|
+
# Retrieve list of markets via WebClient::Connection
|
11
|
+
#
|
12
|
+
# @param opts [Hash] options to retrieve market list with
|
13
|
+
# @option opts [WebClient::Connection] :connection Connection
|
14
|
+
# to use to retrieve market list
|
15
|
+
def self.all(opts={})
|
16
|
+
set_opts(opts)
|
17
|
+
connection.url = "https://api.cryptowat.ch/assets/xrp"
|
18
|
+
|
19
|
+
connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
|
20
|
+
connection.add_plugin Parsers::Market unless connection.plugin?(Parsers::Market)
|
21
|
+
|
22
|
+
connection.perform
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :route
|
26
|
+
|
27
|
+
def initialize(opts={})
|
28
|
+
set_opts(opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_opts(opts={})
|
32
|
+
super opts
|
33
|
+
@route = opts[:route] if opts[:route]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Retrieve list of quotes for market via WebClient::Connection
|
37
|
+
#
|
38
|
+
# @param opts [Hash] options to retrieve quotes with
|
39
|
+
# @option opts [WebClient::Connection] :connection Connection
|
40
|
+
# to use to retrieve quotes
|
41
|
+
def quotes(opts={})
|
42
|
+
set_opts(opts)
|
43
|
+
connection.url = self.route
|
44
|
+
|
45
|
+
connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
|
46
|
+
connection.add_plugin Parsers::Quote unless connection.plugin?(Parsers::Quote)
|
47
|
+
|
48
|
+
connection.perform
|
49
|
+
end
|
50
|
+
end # class Market
|
51
|
+
end # module Model
|
52
|
+
end # module XRBP
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'resolv'
|
3
|
+
|
4
|
+
require_relative './parsers/node'
|
5
|
+
|
6
|
+
module XRBP
|
7
|
+
module Model
|
8
|
+
class Node < Base
|
9
|
+
extend Base::ClassMethods
|
10
|
+
|
11
|
+
DEFAULT_CRAWL_PORT = 51235
|
12
|
+
|
13
|
+
attr_accessor :ip, :port
|
14
|
+
attr_accessor :addr, :version, :uptime, :type
|
15
|
+
|
16
|
+
# Return unique node id
|
17
|
+
def id
|
18
|
+
"#{ip}:#{port}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return node url
|
22
|
+
def url
|
23
|
+
"https://#{ip}:#{port}/crawl"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return bool indicating if this node is valid for crawling
|
27
|
+
def valid?
|
28
|
+
return false unless ip && port
|
29
|
+
|
30
|
+
# ensure no parsing errs
|
31
|
+
begin
|
32
|
+
# FIXME URI.parse is limiting our ability to traverse entire node-set,
|
33
|
+
# some nodes are represented as IPv6 addresses which is throwing
|
34
|
+
# things off.
|
35
|
+
URI.parse(url)
|
36
|
+
rescue
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(o)
|
44
|
+
ip == o.ip && port == o.port
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return new node from the specified url
|
48
|
+
#
|
49
|
+
# @param url [String] node url
|
50
|
+
# @return [Node] new node instance
|
51
|
+
def self.parse_url(url)
|
52
|
+
n = new
|
53
|
+
|
54
|
+
uri = URI.parse(url)
|
55
|
+
n.ip = Resolv.getaddress(uri.host)
|
56
|
+
n.port = uri.port
|
57
|
+
|
58
|
+
n
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return new node from the specified peer object
|
62
|
+
#
|
63
|
+
# @param p [Hash] peer data
|
64
|
+
# @return [Node] new node instance
|
65
|
+
def self.from_peer(p)
|
66
|
+
n = new
|
67
|
+
|
68
|
+
n.addr = p["public_key"]
|
69
|
+
n.ip = p["ip"]
|
70
|
+
n.port = p["port"] || DEFAULT_CRAWL_PORT
|
71
|
+
n.version = p["version"].split("-").last
|
72
|
+
n.uptime = p["uptime"]
|
73
|
+
n.type = p["type"]
|
74
|
+
|
75
|
+
n
|
76
|
+
end
|
77
|
+
|
78
|
+
# Crawl nodes via WebClient::Connection
|
79
|
+
#
|
80
|
+
# @param opts [Hash] options to crawl nodes with
|
81
|
+
# @option opts [WebSocket::Connection] :connection Connection
|
82
|
+
# to use to crawl nodes
|
83
|
+
# @option opts [Integer] :delay optional delay to wait between
|
84
|
+
# crawl iterations
|
85
|
+
def self.crawl(start, opts={})
|
86
|
+
set_opts(opts)
|
87
|
+
delay = opts[:delay] || 1
|
88
|
+
|
89
|
+
queue = Array.new
|
90
|
+
queue << start
|
91
|
+
|
92
|
+
connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
|
93
|
+
connection.add_plugin Parsers::NodePeers unless connection.plugin?(Parsers::NodePeers)
|
94
|
+
|
95
|
+
connection.ssl_verify_peer = false
|
96
|
+
connection.ssl_verify_host = false
|
97
|
+
|
98
|
+
until connection.force_quit?
|
99
|
+
node = queue.shift
|
100
|
+
node = parse_url node unless node.is_a?(Node)
|
101
|
+
|
102
|
+
connection.emit :precrawl, node
|
103
|
+
connection.url = node.url
|
104
|
+
|
105
|
+
peers = connection.perform
|
106
|
+
if peers.nil? || peers.empty?
|
107
|
+
queue << node
|
108
|
+
connection.emit :crawlerr, node
|
109
|
+
connection.rsleep(delay) unless connection.force_quit?
|
110
|
+
next
|
111
|
+
end
|
112
|
+
|
113
|
+
connection.emit :peers, node, peers
|
114
|
+
peers.each { |peer|
|
115
|
+
break if connection.force_quit?
|
116
|
+
|
117
|
+
peer = Node.from_peer peer
|
118
|
+
next unless peer.valid? # skip unless valid
|
119
|
+
|
120
|
+
connection.emit :peer, node, peer
|
121
|
+
queue << peer unless queue.include?(peer)
|
122
|
+
}
|
123
|
+
|
124
|
+
queue << node
|
125
|
+
connection.emit :postcrawl, node
|
126
|
+
connection.rsleep(delay) unless connection.force_quit?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end # module Model
|
131
|
+
end # module XRBP
|