p2ruby 0.1.4 → 0.1.5
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.
- data/HISTORY +4 -0
- data/VERSION +1 -1
- data/bin/order_console +72 -0
- data/bin/script_helper.rb +66 -0
- data/bin/send_once +46 -0
- data/bin/simple_client +8 -0
- data/bin/start_router +4 -0
- data/lib/clients/client.rb +120 -0
- data/lib/clients/exception_wrapper.rb +20 -0
- data/lib/clients/order_console.rb +83 -0
- data/lib/clients/simple_client.rb +264 -0
- metadata +15 -1
data/HISTORY
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.5
|
data/bin/order_console
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: Windows-1251
|
3
|
+
require_relative 'script_helper'
|
4
|
+
require 'clients/order_console'
|
5
|
+
|
6
|
+
# Global functions
|
7
|
+
#####################################
|
8
|
+
def send_message conn, server_address, message_factory
|
9
|
+
$log.puts "Sending sync message..."
|
10
|
+
|
11
|
+
msg = message_factory.message :name => "FutAddOrder",
|
12
|
+
:field => {
|
13
|
+
"P2_Category" => "FORTS_MSG",
|
14
|
+
"P2_Type" => 1,
|
15
|
+
"isin" => "RTS-3.12",
|
16
|
+
:price => "185500",
|
17
|
+
:amount => 1,
|
18
|
+
"client_code" => "001",
|
19
|
+
"type" => 1,
|
20
|
+
"dir" => 1}
|
21
|
+
msg.DestAddr = server_address
|
22
|
+
|
23
|
+
reply = msg.Send(conn, 1000)
|
24
|
+
|
25
|
+
$log.puts reply.parse_reply
|
26
|
+
|
27
|
+
$send = false
|
28
|
+
end
|
29
|
+
|
30
|
+
# Signal Handler (sends signals into main event loop via global variables)
|
31
|
+
####################################
|
32
|
+
Signal.trap("INT") do |signo|
|
33
|
+
puts "Send sync message?"
|
34
|
+
if 'y' == STDIN.gets.chomp
|
35
|
+
$send = true
|
36
|
+
else
|
37
|
+
puts "Interrupted... (#{signo})"
|
38
|
+
$exit = true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Main execution logics
|
43
|
+
#####################################
|
44
|
+
# void ThreadProc(void* name) : Not sure why second event loop is needed?
|
45
|
+
|
46
|
+
$log = ARGV.first == 'log' ? File.new("log/order_console.log", 'w') : STDOUT
|
47
|
+
$exit = false
|
48
|
+
$send = false
|
49
|
+
|
50
|
+
start_router do
|
51
|
+
conn = Clients::ConnectionEvents.new "RbOrderConsole"
|
52
|
+
ds_futinfo = Clients::DataStreamEvents.new conn, "FORTS_FUTINFO"
|
53
|
+
# ds_pos = DataStreamEvents.new conn, "FORTS_POS"
|
54
|
+
# ds_part = DataStreamEvents.new conn, "FORTS_PART"
|
55
|
+
# ds_futtrade = DataStreamEvents.new conn, "FORTS_FUTTRADE"
|
56
|
+
# ds_optinfo = DataStreamEvents.new conn, "FORTS_OPTINFO"
|
57
|
+
# ds_futaggr = DataStreamEvents.new conn, "FORTS_OPTAGGR"
|
58
|
+
# ds_futaggr20 = DataStreamEvents.new conn, "FORTS_FUTAGGR20"
|
59
|
+
# ds_optcommon = DataStreamEvents.new conn, "FORTS_OPTCOMMON"
|
60
|
+
# ds_futcommon = DataStreamEvents.new conn, "FORTS_FUTCOMMON"
|
61
|
+
ds_index = Clients::DataStreamEvents.new conn, "RTS_INDEX"
|
62
|
+
|
63
|
+
server_address = conn.ResolveService("FORTS_SRV")
|
64
|
+
|
65
|
+
puts "Press Ctrl-C to send message or interrupt program"
|
66
|
+
message_factory = P2::MessageFactory.new :ini => MESSAGE_INI
|
67
|
+
|
68
|
+
until $exit
|
69
|
+
send_message(conn, server_address, message_factory) if $send
|
70
|
+
conn.ProcessMessage2(1000)
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'pathname'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
BASE_DIR = (Pathname.new(__FILE__).dirname + '..').realpath.to_s
|
9
|
+
#BASE_DIR = File.expand_path(File.join(File.dirname(File.expand_path(__FILE__)), '..'))
|
10
|
+
SOURCE_DIR = BASE_DIR + '/p2/'
|
11
|
+
TMP_DIR = BASE_DIR + '/tmp/'
|
12
|
+
TEST_DIR = BASE_DIR + '/tmp/p2/'
|
13
|
+
CONFIG_DIR = BASE_DIR + '/config/'
|
14
|
+
INI_DIR = BASE_DIR + '/config/ini/'
|
15
|
+
DATA_DIR = BASE_DIR + '/data/' # For saving incoming (replication) data
|
16
|
+
LIB_DIR = BASE_DIR + '/lib/'
|
17
|
+
|
18
|
+
$LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
|
19
|
+
|
20
|
+
CLIENT_INI = INI_DIR + 'P2ClientGate.ini'
|
21
|
+
MESSAGE_INI = INI_DIR + 'p2fortsgate_messages.ini'
|
22
|
+
TABLESET_INI = INI_DIR + 'rts_index.ini'
|
23
|
+
ROUTER_INI = INI_DIR + 'client_router.ini'
|
24
|
+
ROUTER_PATH = TEST_DIR + 'p2bin/P2MQRouter.exe'
|
25
|
+
ROUTER_TITLE = /P2MQRouter - /
|
26
|
+
|
27
|
+
def prepare_dirs
|
28
|
+
# First we need to prepare clean copy of P2 stand by copying P2 files to /tmp
|
29
|
+
FileUtils.rm_rf TMP_DIR
|
30
|
+
FileUtils.cp_r SOURCE_DIR, TEST_DIR
|
31
|
+
|
32
|
+
# Create temp dirs unless they aready exist
|
33
|
+
FileUtils.mkdir DATA_DIR unless File.exist? DATA_DIR
|
34
|
+
end
|
35
|
+
|
36
|
+
# Starts Router, yields to given block (if any)
|
37
|
+
def start_router opts ={}
|
38
|
+
# Make sure p2ruby gem WAS indeed required...
|
39
|
+
require 'p2ruby' unless defined? P2
|
40
|
+
|
41
|
+
# Find any working router if no opts given
|
42
|
+
router = opts.empty? ? P2::Router.find : nil
|
43
|
+
|
44
|
+
unless router # is already found
|
45
|
+
prepare_dirs
|
46
|
+
router = P2::Router.new :dir => opts[:dir] || TEST_DIR,
|
47
|
+
:path => opts[:path] || ROUTER_PATH,
|
48
|
+
:ini => opts[:ini] || ROUTER_INI,
|
49
|
+
:args => opts[:args], # usually, it's just /ini:,
|
50
|
+
:title => opts[:title] || ROUTER_TITLE,
|
51
|
+
:timeout => opts[:timeout] || 5
|
52
|
+
|
53
|
+
puts "Router started at #{ROUTER_PATH}, establishing uplink..."
|
54
|
+
sleep 0.7
|
55
|
+
end
|
56
|
+
|
57
|
+
if block_given?
|
58
|
+
yield router
|
59
|
+
else
|
60
|
+
router
|
61
|
+
end
|
62
|
+
|
63
|
+
rescue => e
|
64
|
+
puts "Caught in start_router: #{e}"
|
65
|
+
raise e
|
66
|
+
end
|
data/bin/send_once
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative 'script_helper'
|
2
|
+
require 'p2ruby'
|
3
|
+
|
4
|
+
# This script replicates SimpleSend.js functionality
|
5
|
+
start_router do
|
6
|
+
|
7
|
+
# Creating Connection object
|
8
|
+
conn = P2::Connection.new(:ini => CLIENT_INI,
|
9
|
+
:app_name => "RbOrdSend", # ����� ���������� � ������������� ���.
|
10
|
+
:host => "127.0.0.1", # IP �����
|
11
|
+
:port => 4001) # � ���� ���������� �������
|
12
|
+
|
13
|
+
result = conn.Connect() # ������������� ���������� � ��������� ��������
|
14
|
+
|
15
|
+
puts "Connection result: #{result}..."
|
16
|
+
|
17
|
+
server_address = conn.ResolveService("FORTS_SRV") # ���� ����� ������� ������ ������
|
18
|
+
|
19
|
+
puts "FORTS_SRV server address: #{server_address}..."
|
20
|
+
|
21
|
+
# ������� � �������������� ������� ��������-���������
|
22
|
+
msgs = P2::MessageFactory.new :ini => MESSAGE_INI
|
23
|
+
|
24
|
+
puts "Msg Factory inited..."
|
25
|
+
|
26
|
+
# ������� � ��������� ���������
|
27
|
+
msg = msgs.message :name => "FutAddOrder",
|
28
|
+
:dest_addr => server_address,
|
29
|
+
:field => {
|
30
|
+
"P2_Category" => "FORTS_MSG",
|
31
|
+
"P2_Type" => 1,
|
32
|
+
"isin" => "RTS-3.12",
|
33
|
+
:price => "155500",
|
34
|
+
:amount => 1,
|
35
|
+
"client_code" => "001",
|
36
|
+
"type" => 1,
|
37
|
+
"dir" => 1}
|
38
|
+
|
39
|
+
msg.DestAddr = server_address
|
40
|
+
|
41
|
+
puts "Msg created, Sending it..."
|
42
|
+
|
43
|
+
msg = msg.Send(conn, 5000) # ��������, ���� ������ � ������� 5000 �����������
|
44
|
+
|
45
|
+
puts msg.parse_reply #'CP866' #'CP1251'
|
46
|
+
end
|
data/bin/simple_client
ADDED
data/bin/start_router
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'win/time'
|
3
|
+
require 'clients/exception_wrapper'
|
4
|
+
|
5
|
+
# TODO: Change Client into an Interface module, force concrete classes to include it?
|
6
|
+
# Base (non-configureble!)client class, all configuration in-code, subclass it for your specific needs
|
7
|
+
module Clients
|
8
|
+
|
9
|
+
# Base (non-configureble!)client class, all configuration in-code, subclass it for your specific needs
|
10
|
+
class Client
|
11
|
+
# File path constants
|
12
|
+
# TODO: Set paths in calling script, instead of hardcoding here?
|
13
|
+
LOG_PATH = LOG_DIR + 'basic_client.log'
|
14
|
+
REV_PATH = DATA_DIR + 'BasicRevisions.txt'
|
15
|
+
APP_NAME = 'Client'
|
16
|
+
|
17
|
+
include Mix::ExceptionWrapper
|
18
|
+
|
19
|
+
attr_accessor :name, :conn, :logger, :streams, :outputs, :stopped
|
20
|
+
|
21
|
+
# Uniform access to table handlers via @client[:instruments] syntax
|
22
|
+
def [] key
|
23
|
+
send key
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize opts = {}
|
27
|
+
@name = opts[:name] || APP_NAME
|
28
|
+
@logger = opts[:logger] || STDOUT # File.new(LOG_PATH, "w") # System.Text.Encoding.Unicode)
|
29
|
+
@stop = false
|
30
|
+
|
31
|
+
begin
|
32
|
+
# Create Connection object with P2MQRouter connectivity parameters
|
33
|
+
@conn = P2::Connection.new :ini => CLIENT_INI,
|
34
|
+
:host => "localhost",
|
35
|
+
:port => 4001,
|
36
|
+
:AppName => @name
|
37
|
+
# Client will handle Connection's events by default
|
38
|
+
@conn.events.handler = self
|
39
|
+
|
40
|
+
@streams = {}
|
41
|
+
@outputs = []
|
42
|
+
# Run setup for client subclasses
|
43
|
+
setup opts
|
44
|
+
|
45
|
+
# Adding streams stats to outputs:
|
46
|
+
@outputs += @streams.map { |id, stream| [id, stream.stats] }.flatten
|
47
|
+
|
48
|
+
rescue WIN32OLERuntimeError => e
|
49
|
+
puts e
|
50
|
+
if P2.p2_error(e) == P2::P2ERR_INI_FILE_NOT_FOUND #Marshal.GetHRForException(e)
|
51
|
+
puts "Can't find one or both of ini file: P2ClientGate.ini, orders_aggr.ini"
|
52
|
+
end
|
53
|
+
raise e
|
54
|
+
rescue Exception => e #(System.Exception e)
|
55
|
+
puts "Raising non-Win32Ole error in initialize: #{e}"
|
56
|
+
raise e
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Override and set up @streams and @outputs here
|
61
|
+
# (as well as other artifacts specific to your client)
|
62
|
+
def setup opts = {}
|
63
|
+
end
|
64
|
+
|
65
|
+
# Main event cycle
|
66
|
+
def run
|
67
|
+
until @stop
|
68
|
+
try do
|
69
|
+
# (Re)-connecting to Router
|
70
|
+
@conn.Connect()
|
71
|
+
|
72
|
+
# Processing messages in a loop
|
73
|
+
try { process_messages until @stop }
|
74
|
+
|
75
|
+
# Make sure streams are closed and disconnect before reconnecting
|
76
|
+
disconnect
|
77
|
+
end
|
78
|
+
end
|
79
|
+
finalize
|
80
|
+
end
|
81
|
+
|
82
|
+
# Keep alive streams and process messages once
|
83
|
+
def process_messages
|
84
|
+
# Check status for all streams, reopen as necessary
|
85
|
+
@streams.each { |_, stream| try { stream.keep_alive } }
|
86
|
+
|
87
|
+
# Actual processing of incoming messages happens in event callbacks
|
88
|
+
# O����������� ��������� ��������� � ����������� ��������� ������
|
89
|
+
@conn.ProcessMessage2(100)
|
90
|
+
end
|
91
|
+
|
92
|
+
# First close streams, then disconnect connection
|
93
|
+
def disconnect
|
94
|
+
@streams.each { |_, stream| try { stream.finalize } }
|
95
|
+
@conn.Disconnect()
|
96
|
+
end
|
97
|
+
|
98
|
+
# Client's cleanup actions
|
99
|
+
def finalize
|
100
|
+
# Make sure this finalizer runs only once
|
101
|
+
unless @stopping
|
102
|
+
@stop = true
|
103
|
+
@stopping = true
|
104
|
+
disconnect
|
105
|
+
|
106
|
+
@outputs.each { |out| pp out }
|
107
|
+
@stopped = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Handling Connection status change
|
112
|
+
def onConnectionStatusChanged(conn, new_status)
|
113
|
+
puts :info, "MQ connection state " + @conn.status_text(new_status)
|
114
|
+
|
115
|
+
if ((new_status & P2::CS_ROUTER_CONNECTED) != 0)
|
116
|
+
# ����� ����������� - ����������� ����� �������-����������� ?
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end # class Client
|
120
|
+
end # module Clients
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This file contains EventedDataStream class and its helper modules/classes
|
2
|
+
|
3
|
+
module Mix
|
4
|
+
|
5
|
+
# Provides #try method that nicely wraps WIN32OLE exception handling in host classes
|
6
|
+
module ExceptionWrapper
|
7
|
+
# Exception handling wrapper for Win32OLE exceptions.
|
8
|
+
# Catch/log Win32OLE exceptions, pass on all others...
|
9
|
+
def try
|
10
|
+
yield
|
11
|
+
rescue WIN32OLERuntimeError => e
|
12
|
+
puts :error, "Ignoring caught Win32ole runtime error:", e
|
13
|
+
sleep 0.1 # Give other Threads a chance to execute
|
14
|
+
rescue Exception => e
|
15
|
+
self.finalize if respond_to? :finalize
|
16
|
+
puts :error, "Raising non-Win32ole error:", e
|
17
|
+
raise e
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: Windows-1251
|
3
|
+
require 'p2ruby'
|
4
|
+
|
5
|
+
# This script replicates P2AddOrderConsole.cpp functionality (but for async Send)
|
6
|
+
|
7
|
+
module Clients
|
8
|
+
# Event processing classes
|
9
|
+
######################################
|
10
|
+
class ConnectionEvents < P2::Connection
|
11
|
+
def initialize app_name
|
12
|
+
# Create Connection object
|
13
|
+
super :ini => CLIENT_INI, :app_name => app_name,
|
14
|
+
:host => "127.0.0.1", :port => 4001
|
15
|
+
self.events.handler = self
|
16
|
+
self.Connect()
|
17
|
+
end
|
18
|
+
|
19
|
+
# Define Handler for IP2ConnectionEvent event interface
|
20
|
+
def onConnectionStatusChanged(conn, new_status)
|
21
|
+
$log.puts "EVENT ConnectionStatusChanged: #{conn} - #{status_text new_status}"
|
22
|
+
end
|
23
|
+
end # class ConnectionEvents
|
24
|
+
|
25
|
+
#####################################
|
26
|
+
class DataStreamEvents < P2::DataStream
|
27
|
+
def initialize conn, short_name
|
28
|
+
# Create DataStream object
|
29
|
+
super :stream_name => "#{short_name}_REPL", :type => P2::RT_COMBINED_DYNAMIC #,
|
30
|
+
# :DBConnString => "P2DBSqLiteD.dll;;Log\\#{short_name}_.db"
|
31
|
+
self.events.handler = self
|
32
|
+
self.Open(conn)
|
33
|
+
end
|
34
|
+
|
35
|
+
def wrap(rec)
|
36
|
+
P2::Record.new :ole => rec
|
37
|
+
end
|
38
|
+
|
39
|
+
# Define Handlers for IP2DataStreamEvents event interface
|
40
|
+
def onStreamStateChanged(stream, new_state)
|
41
|
+
$log.puts "StreamStateChanged #{stream.StreamName} - #{state_text(new_state)}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def onStreamDataInserted stream, table_name, rec
|
45
|
+
return unless table_name == 'sys_messages' # Single out one table events
|
46
|
+
$log.puts "StreamDataInserted #{stream.StreamName} - #{table_name}: #{wrap(rec)}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def onStreamDataUpdated(stream, table_name, id, rec)
|
50
|
+
$log.puts "StreamDataUpdated #{stream.StreamName} - #{table_name} - #{id}: #{wrap(rec)}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def onStreamDataDeleted(stream, table_name, id, rec)
|
54
|
+
$log.puts "StreamDataDeleted #{stream.StreamName} - #{table_name} - #{id}: #{wrap(rec)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def onStreamDatumDeleted(stream, table_name, rev)
|
58
|
+
$log.puts "StreamDatumDeleted #{stream.StreamName} - #{table_name} - #{rev}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def onStreamDBWillBeDeleted(stream)
|
62
|
+
$log.puts "StreamDBWillBeDeleted #{stream.StreamName} "
|
63
|
+
end
|
64
|
+
|
65
|
+
def onStreamLifeNumChanged(stream, life_num)
|
66
|
+
$log.puts "StreamLifeNumChanged #{stream.StreamName} - #{life_num} "
|
67
|
+
end
|
68
|
+
|
69
|
+
def onStreamDataBegin(stream)
|
70
|
+
$log.puts "StreamDataBegin #{stream.StreamName} "
|
71
|
+
end
|
72
|
+
|
73
|
+
def onStreamDataEnd(stream)
|
74
|
+
$log.puts "StreamDataEnd #{stream.StreamName} "
|
75
|
+
end
|
76
|
+
end # class DataStreamEvents
|
77
|
+
|
78
|
+
end # module Clients
|
79
|
+
|
80
|
+
#####################################
|
81
|
+
#class CAsyncMessageEvent : Not used, Async Send event interfaces not implemented :(
|
82
|
+
#####################################
|
83
|
+
#class CAsyncSendEvent2 : Not used, Async Send event interfaces not implemented :(
|
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'p2ruby'
|
3
|
+
|
4
|
+
# Naive all-it-one Client implementation that handles all events internally
|
5
|
+
# and uses original P2 as its namespace
|
6
|
+
|
7
|
+
# File path constants
|
8
|
+
LOG_PATH = 'log\simple_client.log'
|
9
|
+
AGGR_PATH = 'data\SaveAggrRev.txt'
|
10
|
+
DEAL_PATH = 'data\SaveDeal.txt'
|
11
|
+
|
12
|
+
# Replication Stream parameters
|
13
|
+
AGGR_INI = INI_DIR + 'orders_aggr.ini'
|
14
|
+
DEAL_INI = INI_DIR + 'fut_trades.ini'
|
15
|
+
AGGR_ID = 'FORTS_FUTAGGR20_REPL'
|
16
|
+
DEAL_ID = 'FORTS_FUTTRADE_REPL'
|
17
|
+
|
18
|
+
## Global functions
|
19
|
+
|
20
|
+
# Normal log (STDOUT)
|
21
|
+
def log
|
22
|
+
STDOUT
|
23
|
+
# @log_file ||= File.new(LOG_PATH, "w") # System.Text.Encoding.Unicode)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Error log (for unexpected events/exceptions)
|
27
|
+
def error_log
|
28
|
+
@error_log_file ||= File.new(LOG_PATH, "w") # System.Text.Encoding.Unicode)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Extensions to P2Ruby library classes
|
32
|
+
module P2
|
33
|
+
# Reopening P2::DataStream class to hack in #keep_alive method.
|
34
|
+
class DataStream < Base
|
35
|
+
# (Re)-opens stale data stream, optionally resetting table revisions of its TableSet
|
36
|
+
def keep_alive(conn, revisions={})
|
37
|
+
if closed? || error?
|
38
|
+
self.Close() if error?
|
39
|
+
revisions.each { |table, rev| self.TableSet.Rev[table.to_s] = rev } if self.TableSet
|
40
|
+
self.Open(conn)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module P2 # P2SimpleGate2Client
|
47
|
+
|
48
|
+
# Event proxy that collects event statistics
|
49
|
+
class Stats
|
50
|
+
def initialize real_handler
|
51
|
+
@real_handler = real_handler
|
52
|
+
@stats = {}
|
53
|
+
|
54
|
+
# Mock all event processing methods of real event handler
|
55
|
+
@real_handler.methods.select { |m| m =~/^on/ }.each do |method|
|
56
|
+
self.define_singleton_method(method) do |stream, key, *args|
|
57
|
+
@stats[method] ||= Hash.new(0)
|
58
|
+
@stats[method][key] += 1
|
59
|
+
@real_handler.send method, stream, key, *args
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
@stats
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
@stats
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Simple all-it-one client that handles all events internally
|
74
|
+
class SimpleClient
|
75
|
+
|
76
|
+
attr_accessor :stats
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
@stop = false
|
80
|
+
|
81
|
+
begin
|
82
|
+
# Create Connection object with P2MQRouter connectivity parameters
|
83
|
+
@conn = P2::Connection.new :ini => CLIENT_INI,
|
84
|
+
:host => "localhost",
|
85
|
+
:port => 4001,
|
86
|
+
:AppName => "p2ruby_baseless"
|
87
|
+
|
88
|
+
# Load previous table revisions of data streams
|
89
|
+
@current_rev = {"orders_aggr" => load_rev(AGGR_PATH),
|
90
|
+
"deal" => load_rev(DEAL_PATH)}
|
91
|
+
|
92
|
+
# Open files for writing received data (and tracking table revisions)
|
93
|
+
@aggr_file ||= File.new(AGGR_PATH, "w") # System.Text.Encoding.Unicode)
|
94
|
+
@deal_file ||= File.new(DEAL_PATH, "w") # System.Text.Encoding.Unicode)
|
95
|
+
|
96
|
+
# Initialize TableSets with scheme and revision data
|
97
|
+
deal_tables = P2::TableSet.new :ini => DEAL_INI,
|
98
|
+
:rev => {"deal" => @current_rev["deal"] + 1}
|
99
|
+
aggr_tables = P2::TableSet.new :ini => AGGR_INI,
|
100
|
+
:rev => {"orders_aggr" =>
|
101
|
+
@current_rev["orders_aggr"] + 1}
|
102
|
+
|
103
|
+
# Create "replication data stream" object for aggregated orders info
|
104
|
+
@aggr_stream = P2::DataStream.new :type => P2::RT_COMBINED_DYNAMIC,
|
105
|
+
:name => AGGR_ID,
|
106
|
+
:TableSet => aggr_tables
|
107
|
+
|
108
|
+
# Create "replication data stream" object for incoming trades/deals info
|
109
|
+
@deal_stream = P2::DataStream.new :type => P2::RT_COMBINED_DYNAMIC,
|
110
|
+
:name => DEAL_ID,
|
111
|
+
:TableSet => deal_tables
|
112
|
+
# @deal_stream.TableSet.InitFromIni2("forts_scheme.ini", "FutTrade")
|
113
|
+
|
114
|
+
# Create Stats objects that collect event statistics
|
115
|
+
@stats = {AGGR_ID => Stats.new(self),
|
116
|
+
DEAL_ID => Stats.new(self)}
|
117
|
+
|
118
|
+
# Register event handlers for Connection and Data Stream events
|
119
|
+
@conn.events.handler = self
|
120
|
+
@aggr_stream.events.handler = @stats[AGGR_ID] # self
|
121
|
+
@deal_stream.events.handler = @stats[DEAL_ID] # self
|
122
|
+
|
123
|
+
rescue WIN32OLERuntimeError => e
|
124
|
+
log.puts e
|
125
|
+
if P2.p2_error(e) == P2::P2ERR_INI_PATH_NOT_FOUND #Marshal.GetHRForException(e)
|
126
|
+
error_log.puts "Can't find one or both of ini file: P2ClientGate.ini, orders_aggr.ini"
|
127
|
+
end
|
128
|
+
raise e
|
129
|
+
rescue Exception => e #(System.Exception e)
|
130
|
+
log.puts "Raising non-Win32Ole error in initialize:", e
|
131
|
+
raise e
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Exception handling wrapper for Win32OLE exceptions.
|
136
|
+
# Catch/log Win32OLE exceptions, pass on all others...
|
137
|
+
def try
|
138
|
+
begin
|
139
|
+
yield
|
140
|
+
rescue WIN32OLERuntimeError => e #(System.Runtime.InteropServices.COMException e)
|
141
|
+
log.puts "Ignoring caught Win32Ole runtime error:", e
|
142
|
+
rescue Exception => e #(System.Exception e)
|
143
|
+
pp @stats
|
144
|
+
log.puts "Raising non-Win32Ole error:", e
|
145
|
+
raise e
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Main event cycle
|
150
|
+
def run
|
151
|
+
until @stop
|
152
|
+
try do
|
153
|
+
# (Re)-connecting to Router
|
154
|
+
@conn.Connect()
|
155
|
+
try do
|
156
|
+
until @stop
|
157
|
+
try do
|
158
|
+
@aggr_stream.keep_alive @conn, :quotes => @current_rev["orders_aggr"] + 1
|
159
|
+
@deal_stream.keep_alive @conn, :deal => @current_rev["deal"] + 1
|
160
|
+
end
|
161
|
+
# Actual processing of incoming messages happens in event callbacks
|
162
|
+
# O����������� ��������� ��������� � ����������� ��������� ������
|
163
|
+
@conn.ProcessMessage2(100)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
try { @aggr_stream.Close() } unless @aggr_stream.closed?
|
168
|
+
try { @deal_stream.Close() } unless @deal_stream.closed?
|
169
|
+
|
170
|
+
@conn.Disconnect()
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Handling Connection status change
|
176
|
+
def onConnectionStatusChanged(conn, new_status)
|
177
|
+
state = "MQ connection state " + @conn.status_text(new_status)
|
178
|
+
|
179
|
+
if ((new_status & P2::CS_ROUTER_CONNECTED) != 0)
|
180
|
+
# ����� ����������� - ����������� ����� �������-�����������
|
181
|
+
end
|
182
|
+
log.puts(state)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Handling replication Data Stream status change
|
186
|
+
def onStreamStateChanged(stream, new_state)
|
187
|
+
state = "Stream #{stream.StreamName} state: #{@deal_stream.state_text(new_state)}"
|
188
|
+
case new_state
|
189
|
+
when P2::DS_STATE_CLOSE, P2::DS_STATE_ERROR
|
190
|
+
# @opened = false
|
191
|
+
end
|
192
|
+
log.puts(state)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Insert record
|
196
|
+
def onStreamDataInserted(stream, table_name, rec)
|
197
|
+
# Interrupt inside event hook bubbles up instead of being caught in main loop...
|
198
|
+
try do
|
199
|
+
log.puts "Stream #{stream.StreamName} inserts into #{table_name} "
|
200
|
+
|
201
|
+
if stream.StreamName == AGGR_ID
|
202
|
+
# This is FORTS_FUTAGGR20_REPL stream event
|
203
|
+
save_aggr(rec, table_name, stream)
|
204
|
+
|
205
|
+
elsif stream.StreamName == DEAL_ID && table_name == 'deal'
|
206
|
+
# This is FORTS_FUTTRADE_REPL stream event
|
207
|
+
# !!!! Saving only records from 'deal' table, not heartbeat or multileg_deal
|
208
|
+
save_data(rec, table_name, @deal_file, stream, "\n", '')
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Delete record
|
214
|
+
def onStreamDataDeleted(stream, table_name, id, rec)
|
215
|
+
log.puts "Stream #{stream.StreamName} deletes #{id} from #{table_name} "
|
216
|
+
if stream.StreamName == AGGR_ID
|
217
|
+
save_aggr(rec, table_name, stream)
|
218
|
+
else
|
219
|
+
error_log.puts 'Unexpected onStreamDataDeleted event'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Stream LifeNum change
|
224
|
+
def onStreamLifeNumChanged(stream, life_num)
|
225
|
+
if (stream.StreamName == AGGR_ID)
|
226
|
+
@aggr_stream.TableSet.LifeNum = life_num
|
227
|
+
@aggr_stream.TableSet.SetLifeNumToIni(AGGR_INI)
|
228
|
+
end
|
229
|
+
if (stream.StreamName == DEAL_ID)
|
230
|
+
@deal_stream.TableSet.LifeNum = life_num
|
231
|
+
@deal_stream.TableSet.SetLifeNumToIni(DEAL_INI)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Load latest revision from file with given path,
|
236
|
+
# return 0 if no file or saved revision available
|
237
|
+
def load_rev file_path
|
238
|
+
if File.exists? file_path
|
239
|
+
File.open(file_path) do |file|
|
240
|
+
line = file.readlines.select { |l| l =~ /^replRev/ }.last
|
241
|
+
rev = line ? line.split('=')[1] : nil
|
242
|
+
rev.chomp.to_i if rev
|
243
|
+
end
|
244
|
+
end || 0
|
245
|
+
end
|
246
|
+
|
247
|
+
# Save/log aggregate orders record
|
248
|
+
def save_aggr(rec, table_name, stream)
|
249
|
+
save_data(rec, table_name, log, stream)
|
250
|
+
@aggr_file.puts "replRev=#{@current_rev['orders_aggr']}"
|
251
|
+
@aggr_file.flush
|
252
|
+
end
|
253
|
+
|
254
|
+
# Save/log given record data
|
255
|
+
def save_data(rec, table_name, file, stream, divider = '; ', finalizer = nil)
|
256
|
+
@current_rev[table_name] = rec.GetValAsLongByIndex(1)
|
257
|
+
|
258
|
+
fields = stream.TableSet.FieldList(table_name) #"deal"]
|
259
|
+
file.puts fields.split(',').map { |f| "#{f}=#{rec.GetValAsString(f)}" }.join divider
|
260
|
+
file.puts(finalizer) if finalizer
|
261
|
+
file.flush
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: p2ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.1.
|
5
|
+
version: 0.1.5
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- arvicco
|
@@ -39,6 +39,11 @@ description: Ruby bindings and wrapper classes for P2ClientGate
|
|
39
39
|
email: arvitallian@gmail.com
|
40
40
|
executables:
|
41
41
|
- olegen.rb
|
42
|
+
- order_console
|
43
|
+
- script_helper.rb
|
44
|
+
- send_once
|
45
|
+
- simple_client
|
46
|
+
- start_router
|
42
47
|
extensions: []
|
43
48
|
|
44
49
|
extra_rdoc_files:
|
@@ -47,6 +52,15 @@ extra_rdoc_files:
|
|
47
52
|
- README.rdoc
|
48
53
|
files:
|
49
54
|
- bin/olegen.rb
|
55
|
+
- bin/order_console
|
56
|
+
- bin/script_helper.rb
|
57
|
+
- bin/send_once
|
58
|
+
- bin/simple_client
|
59
|
+
- bin/start_router
|
60
|
+
- lib/clients/client.rb
|
61
|
+
- lib/clients/exception_wrapper.rb
|
62
|
+
- lib/clients/order_console.rb
|
63
|
+
- lib/clients/simple_client.rb
|
50
64
|
- lib/extension.rb
|
51
65
|
- lib/ole20110223-013209.rb
|
52
66
|
- lib/p2ruby/application.rb
|