cotcube-helpers 0.2.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/Gemfile +1 -0
- data/VERSION +1 -1
- data/bin/gitlog +4 -0
- data/bin/uncommitted +28 -0
- data/cotcube-helpers.gemspec +1 -1
- data/lib/cotcube-helpers/array_ext.rb +52 -27
- data/lib/cotcube-helpers/cache_client.rb +52 -0
- data/lib/cotcube-helpers/constants.rb +2 -0
- data/lib/cotcube-helpers/data_client.rb +2 -2
- data/lib/cotcube-helpers/datetime_ext.rb +13 -5
- data/lib/cotcube-helpers/deep_decode_datetime.rb +25 -0
- data/lib/cotcube-helpers/hash_ext.rb +31 -5
- data/lib/cotcube-helpers/init.rb +5 -3
- data/lib/cotcube-helpers/josch_client.rb +115 -0
- data/lib/cotcube-helpers/order_client.rb +131 -0
- data/lib/cotcube-helpers/string_ext.rb +4 -0
- data/lib/cotcube-helpers.rb +4 -1
- data/scripts/cron_ruby_wrapper.sh +2 -1
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 569d3d98802f31608b3525e0b0692a19a67b65a05036f7b9c04cd4d8985017d3
|
4
|
+
data.tar.gz: ad92e036d65e317ab94f179020b4d131ebb085e8ff87395f17ac5c65b0ec02a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca30750b5450a285efc24f7d8498919b329fddc848e8eaee24990632701e529ab967aaf4c94d6718858f921297674adaaee36025a2fce2664d11e370e0003a86
|
7
|
+
data.tar.gz: 7b85764c5630f909f87c0987e662b6801e20e22de850945342dddc839a67c7636abe284463f4e5fba1c952aec80e51dce25f24a1795e9c9a6cdb96e8b29907e6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
## 0.2.4 (January 13, 2022)
|
2
|
+
- minor changes to init and bin/uncommitted
|
3
|
+
- datetime_ext: added #seconds_until_next_minute
|
4
|
+
- hash: added #deep_dup and #reduce_group; array: added #deep_dup
|
5
|
+
- bin/gitlog: pretty formatted git log
|
6
|
+
- string_ext: escape_regex escapes characters that otherwise have functional meanings in a regex
|
7
|
+
- array_ext: elem_raises? checks for each elem if raises with block, return raising elems or false
|
8
|
+
- Merge branch 'main' of github.com:donkeybridge/cotcube-helpers into main
|
9
|
+
- merging conflict
|
10
|
+
|
11
|
+
## 0.2.3 (December 30, 2021)
|
12
|
+
- merging conflict
|
13
|
+
- added bare josch_ and order_client s
|
14
|
+
- added bare js_ and order_client s
|
15
|
+
|
16
|
+
## 0.2.2.5 (December 23, 2021)
|
17
|
+
- hash_ext: reworked keys_to_sym!
|
18
|
+
- minor changes
|
19
|
+
- deep_decode_datetime: added helper for conversion of timestrings in nested arrays and hashed
|
20
|
+
- cache_client: rewritten as class
|
21
|
+
- gemspec: raised activesupport to version 7
|
22
|
+
|
23
|
+
## 0.2.2.4 (December 07, 2021)
|
24
|
+
- introduced cache_client as client to readcache
|
25
|
+
|
1
26
|
## 0.2.2.3 (November 28, 2021)
|
2
27
|
- data_client: fixing troublesome output in .command
|
3
28
|
|
data/Gemfile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.4
|
data/bin/gitlog
ADDED
data/bin/uncommitted
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
ROOT=${1:-$HOME/GEMS}
|
3
|
+
|
4
|
+
|
5
|
+
check_dir () {
|
6
|
+
CYAN="\033[1;36m"
|
7
|
+
RESET="\033[0m"
|
8
|
+
VGREP="grep --color=always -v"
|
9
|
+
dir=$1
|
10
|
+
if [ -d "${dir}/.git" ]; then
|
11
|
+
echo -e ${CYAN}${dir}${RESET}
|
12
|
+
pushd ${dir} 2>&1 >/dev/null
|
13
|
+
git status 2>&1 | $VGREP 'no changes added to commit' |\
|
14
|
+
$VGREP '^$' |\
|
15
|
+
$VGREP 'to include in what will' |\
|
16
|
+
$VGREP '(use "git ' |\
|
17
|
+
$VGREP 'On branch main' |\
|
18
|
+
$VGREP 'On branch master' |\
|
19
|
+
$VGREP 'nothing to commit' |\
|
20
|
+
$VGREP 'Your branch is up to date'
|
21
|
+
popd 2>&1 >/dev/null
|
22
|
+
fi
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
export -f check_dir
|
27
|
+
|
28
|
+
find $ROOT -type d -maxdepth 1 2>/dev/null | xargs -n 1 bash -c 'check_dir "$1"' _
|
data/cotcube-helpers.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ['lib']
|
28
28
|
|
29
|
-
spec.add_dependency 'activesupport', '~>
|
29
|
+
spec.add_dependency 'activesupport', '~> 7'
|
30
30
|
spec.add_dependency 'colorize', '~> 0.8'
|
31
31
|
|
32
32
|
|
@@ -64,38 +64,63 @@ class Array
|
|
64
64
|
def select_within(ranges:, attr: nil, &block)
|
65
65
|
unless attr.nil? || first[attr]
|
66
66
|
raise ArgumentError,
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
67
|
+
"At least first element of Array '#{first}' does not contain attr '#{attr}'!"
|
68
|
+
end
|
69
|
+
raise ArgumentError, 'Ranges should be an Array or, more precisely, respond_to :map' unless ranges.respond_to? :map
|
70
|
+
raise ArgumentError, 'Each range in :ranges should respond to .include!' unless ranges.map do |x|
|
71
|
+
x.respond_to? :include?
|
72
|
+
end.reduce(:&)
|
73
|
+
|
74
|
+
select do |el|
|
75
|
+
value = attr.nil? ? el : el[attr]
|
76
|
+
ranges.map do |range|
|
77
|
+
range.include?(block.nil? ? value : block.call(value))
|
78
|
+
end.reduce(:|)
|
79
|
+
end
|
79
80
|
end
|
80
|
-
end
|
81
81
|
|
82
|
-
def select_right_by(inclusive: false, exclusive: false, initial: [], &block)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
82
|
+
def select_right_by(inclusive: false, exclusive: false, initial: [], &block)
|
83
|
+
# unless range.is_a? Range and
|
84
|
+
# (range.begin.nil? or range.begin.is_a?(Integer)) and
|
85
|
+
# (range.end.nil? or range.end.is_a?(Integer))
|
86
|
+
# raise ArgumentError, ":range, if given, must be a range of ( nil|Integer..nil|Integer), got '#{range}'"
|
87
|
+
# end
|
88
|
+
|
89
|
+
raise ArgumentError, 'No block given.' unless block.is_a? Proc
|
90
|
+
|
91
|
+
inclusive = true unless exclusive
|
92
|
+
if inclusive && exclusive
|
93
|
+
raise ArgumentError,
|
94
|
+
"Either :inclusive or :exclusive must remain falsey, got '#{inclusive}' and '#{exclusive}'"
|
95
|
+
end
|
96
|
+
|
97
|
+
index = find_index { |obj| block.call(obj) }
|
88
98
|
|
89
|
-
|
99
|
+
self[((inclusive ? index : index + 1)..)]
|
100
|
+
end
|
90
101
|
|
91
|
-
|
92
|
-
|
93
|
-
raise ArgumentError,
|
94
|
-
|
102
|
+
def elem_raises?(&block)
|
103
|
+
raise ArgumentError, "Must provide a block." unless block_given?
|
104
|
+
raise ArgumentError, "Block must have arity of 1." unless block.arity == 1
|
105
|
+
map do |elem|
|
106
|
+
begin
|
107
|
+
block.call(elem)
|
108
|
+
false
|
109
|
+
rescue
|
110
|
+
elem
|
111
|
+
end
|
112
|
+
end.reject{|z| z.is_a? FalseClass }.tap{|z| z.empty? ? (return false) : (return z)}
|
95
113
|
end
|
96
114
|
|
97
|
-
|
115
|
+
def deep_dup
|
116
|
+
map do |el|
|
117
|
+
case el
|
118
|
+
when Hash, Array
|
119
|
+
el.deep_dup
|
120
|
+
else
|
121
|
+
el.dup
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
98
125
|
|
99
|
-
self[((inclusive ? index : index + 1)..)]
|
100
|
-
end
|
101
126
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Cotcube
|
5
|
+
module Helpers
|
6
|
+
class CacheClient
|
7
|
+
|
8
|
+
def initialize(query='keys', timezone: Cotcube::Helpers::CHICAGO, debug: false, deflate: false, update: false)
|
9
|
+
raise ArgumentError, "Query must not be empty." if [ nil, '' ].include? query
|
10
|
+
raise ArgumentError, "Query '#{query}' is garbage." if query.split('/').size > 2 or not query.match? /\A[a-zA-Z0-9?=\/]+\Z/
|
11
|
+
@update = update ? '?update=true' : ''
|
12
|
+
@request_headers = {}
|
13
|
+
@request_headers['Accept-Encoding'] = 'deflate' if deflate
|
14
|
+
@query = query
|
15
|
+
@result = JSON.parse(HTTParty.get("http://100.100.0.14:8081/#{query}#{@update}").body, headers: @request_headers, symbolize_names: true) rescue { error: 1, msg: "Could not parse response for query '#{query}'." }
|
16
|
+
retry_once if has_errors?
|
17
|
+
end
|
18
|
+
|
19
|
+
def retry_once
|
20
|
+
sleep 2
|
21
|
+
raw = HTTParty.get("http://100.100.0.14:8081/#{query}#{update}")
|
22
|
+
@result = JSON.parse(raw.body, symbolize_names: true) rescue { error: 1, msg: "Could not parse response for query '#{query}'." }
|
23
|
+
if has_errors?
|
24
|
+
puts "ERROR in parsing response: #{raw[..300]}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_errors?
|
29
|
+
result[:error].nil? or result[:error] > 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def warnings
|
33
|
+
result[:warnings]
|
34
|
+
end
|
35
|
+
|
36
|
+
def payload
|
37
|
+
has_errors? ? false : @result[:payload]
|
38
|
+
end
|
39
|
+
|
40
|
+
def entity
|
41
|
+
query.split('/').first
|
42
|
+
end
|
43
|
+
|
44
|
+
def asset
|
45
|
+
entity, asset = query.split('/')
|
46
|
+
asset
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :query, :result, :update
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -44,7 +44,7 @@ module Cotcube
|
|
44
44
|
# otherwise times out --- the counterpart here is the subscription within
|
45
45
|
# setup_reply_queue
|
46
46
|
#
|
47
|
-
def command(command, timeout:
|
47
|
+
def command(command, timeout: 10)
|
48
48
|
command = { command: command.to_s } unless command.is_a? Hash
|
49
49
|
command[:timestamp] ||= (Time.now.to_f * 1000).to_i
|
50
50
|
request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
|
@@ -227,4 +227,4 @@ begin
|
|
227
227
|
}
|
228
228
|
ensure
|
229
229
|
client.stop
|
230
|
-
|
230
|
+
end
|
@@ -12,6 +12,14 @@ class DateTime
|
|
12
12
|
end
|
13
13
|
|
14
14
|
alias to_sssm to_seconds_since_sunday_morning
|
15
|
+
|
16
|
+
def seconds_until_next_minute(offset: 60)
|
17
|
+
offset = offset % 60
|
18
|
+
offset = 60 if offset.zero?
|
19
|
+
seconds = (self + offset - (self.to_f % 60).round(3) - self).to_f
|
20
|
+
seconds + (seconds.negative? ? 60 : 0)
|
21
|
+
end
|
22
|
+
|
15
23
|
end
|
16
24
|
|
17
25
|
class Date
|
@@ -21,12 +29,12 @@ class Date
|
|
21
29
|
form = '%Y %W %w'
|
22
30
|
build_range = lambda {|w|
|
23
31
|
begin
|
24
|
-
|
25
|
-
|
32
|
+
( DateTime.strptime("#{year} #{w} 1", form).to_date..
|
33
|
+
DateTime.strptime("#{year} #{w} 0", form).to_date)
|
26
34
|
rescue
|
27
|
-
|
28
|
-
|
29
|
-
|
35
|
+
# beyond Dec 31st #strptime must be called with cw:0 to keep it working
|
36
|
+
( DateTime.strptime("#{year} #{w} 1", form).to_date..
|
37
|
+
DateTime.strptime("#{year+1} 0 0", form).to_date)
|
30
38
|
end
|
31
39
|
}
|
32
40
|
case week
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Cotcube
|
2
|
+
module Helpers
|
3
|
+
VALID_DATETIME_STRING = lambda {|str| str.is_a?(String) and [10,25,29].include?(str.length) and str.count("^0-9:TZ+-= ").zero? }
|
4
|
+
|
5
|
+
def deep_decode_datetime(data, zone: DateTime)
|
6
|
+
case data
|
7
|
+
when nil; nil
|
8
|
+
when VALID_DATETIME_STRING
|
9
|
+
res = nil
|
10
|
+
begin
|
11
|
+
res = zone.parse(data)
|
12
|
+
rescue ArgumentError
|
13
|
+
data
|
14
|
+
end
|
15
|
+
[ DateTime, ActiveSupport::TimeWithZone ].include?(res.class) ? res : data
|
16
|
+
when Array; data.map! { |d| deep_decode_datetime(d, zone: zone) }
|
17
|
+
when Hash; data.transform_values! { |v| deep_decode_datetime(v, zone: zone) }
|
18
|
+
else; data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module_function :deep_decode_datetime
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -2,16 +2,42 @@
|
|
2
2
|
|
3
3
|
# Monkey patching the Ruby Core class Hash
|
4
4
|
class Hash
|
5
|
-
def keys_to_sym
|
6
|
-
|
5
|
+
def keys_to_sym!
|
6
|
+
self.keys.each do |key|
|
7
7
|
case self[key].class.to_s
|
8
8
|
when 'Hash'
|
9
|
-
self[key].keys_to_sym
|
9
|
+
self[key].keys_to_sym!
|
10
10
|
when 'Array'
|
11
|
-
self[key].map { |el| el.is_a?(Hash) ? el.keys_to_sym : el }
|
11
|
+
self[key].map { |el| el.is_a?(Hash) ? el.keys_to_sym! : el }
|
12
12
|
end
|
13
|
-
self[key.to_sym] = delete(key)
|
13
|
+
self["#{key}".to_sym] = delete(key)
|
14
14
|
end
|
15
15
|
self
|
16
16
|
end
|
17
|
+
|
18
|
+
# a group_hash was created from an array by running group_by
|
19
|
+
# to reduce a group_hash, the given block is applied to each array of the hash
|
20
|
+
# if its not an array value, the block will auto-yield nil
|
21
|
+
def reduce_group(&block)
|
22
|
+
raise ArgumentError, 'No block given' unless block_given?
|
23
|
+
map do |key,value|
|
24
|
+
case value
|
25
|
+
when Array
|
26
|
+
[key, (block.call(value) rescue nil) ]
|
27
|
+
else
|
28
|
+
[key, nil]
|
29
|
+
end
|
30
|
+
end.to_h
|
31
|
+
end
|
32
|
+
|
33
|
+
def deep_dup
|
34
|
+
map do |k,v|
|
35
|
+
case v
|
36
|
+
when Hash, Array
|
37
|
+
[k, v.deep_dup]
|
38
|
+
else
|
39
|
+
[k, v.dup]
|
40
|
+
end
|
41
|
+
end.to_h
|
42
|
+
end
|
17
43
|
end
|
data/lib/cotcube-helpers/init.rb
CHANGED
@@ -22,9 +22,10 @@ module Cotcube
|
|
22
22
|
def init(config_file_name: nil,
|
23
23
|
gem_name: nil,
|
24
24
|
debug: false)
|
25
|
-
gem_name
|
26
|
-
|
27
|
-
|
25
|
+
gem_name ||= self.ancestors.first.to_s
|
26
|
+
name = gem_name.split('::').last.downcase
|
27
|
+
config_file_name = "#{name}.yml"
|
28
|
+
config_file = config_path + "/#{config_file_name}"
|
28
29
|
|
29
30
|
if File.exist?(config_file)
|
30
31
|
require 'yaml'
|
@@ -35,6 +36,7 @@ module Cotcube
|
|
35
36
|
|
36
37
|
defaults = {
|
37
38
|
data_path: '/var/cotcube/' + name,
|
39
|
+
pid_file: "/var/run/cotcube/#{name}.pid"
|
38
40
|
}
|
39
41
|
|
40
42
|
config = defaults.merge(config)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bunny'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Cotcube
|
8
|
+
module Helpers
|
9
|
+
class JoSchClient
|
10
|
+
SECRETS_DEFAULT = {
|
11
|
+
'josch_mq_proto' => 'http',
|
12
|
+
'josch_mq_user' => 'guest',
|
13
|
+
'josch_mq_password' => 'guest',
|
14
|
+
'josch_mq_host' => 'localhost',
|
15
|
+
'josch_mq_port' => '15672',
|
16
|
+
'josch_mq_vhost' => '%2F'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
SECRETS = SECRETS_DEFAULT.merge(
|
20
|
+
lambda {
|
21
|
+
begin
|
22
|
+
YAML.safe_load(File.read(Cotcube::Helpers.init[:secrets_file]))
|
23
|
+
rescue StandardError
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
}.call
|
27
|
+
)
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@connection = Bunny.new(user: SECRETS['josch_mq_user'],
|
31
|
+
password: SECRETS['josch_mq_password'],
|
32
|
+
vhost: SECRETS['josch_mq_vhost'])
|
33
|
+
@connection.start
|
34
|
+
|
35
|
+
@commands = connection.create_channel
|
36
|
+
@exchange = commands.direct('josch_commands')
|
37
|
+
@requests = {}
|
38
|
+
@debug = false
|
39
|
+
setup_reply_queue
|
40
|
+
end
|
41
|
+
|
42
|
+
# command acts a synchronizer: it sends the command and waits for the response
|
43
|
+
# otherwise times out --- the counterpart here is the subscription within
|
44
|
+
# setup_reply_queue
|
45
|
+
#
|
46
|
+
def command(command, timeout: 10)
|
47
|
+
command = { command: command.to_s } unless command.is_a? Hash
|
48
|
+
command[:timestamp] ||= (Time.now.to_f * 1000).to_i
|
49
|
+
request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
|
50
|
+
requests[request_id] = {
|
51
|
+
request: command,
|
52
|
+
id: request_id,
|
53
|
+
lock: Mutex.new,
|
54
|
+
condition: ConditionVariable.new
|
55
|
+
}
|
56
|
+
|
57
|
+
exchange.publish(command.to_json,
|
58
|
+
routing_key: 'josch_commands',
|
59
|
+
correlation_id: request_id,
|
60
|
+
reply_to: reply_queue.name)
|
61
|
+
|
62
|
+
# wait for the signal to continue the execution
|
63
|
+
#
|
64
|
+
requests[request_id][:lock].synchronize do
|
65
|
+
requests[request_id][:condition].wait(requests[request_id][:lock], timeout)
|
66
|
+
end
|
67
|
+
|
68
|
+
# if we reached timeout, we will return nil, just for explicity
|
69
|
+
response = requests[request_id][:response].dup
|
70
|
+
requests.delete(request_id)
|
71
|
+
response
|
72
|
+
end
|
73
|
+
|
74
|
+
alias_method :send_command, :command
|
75
|
+
|
76
|
+
attr_accessor :response
|
77
|
+
attr_reader :lock, :condition
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
attr_reader :call_id, :connection, :requests, :persistent,
|
82
|
+
:commands, :server_queue_name, :reply_queue, :exchange
|
83
|
+
|
84
|
+
def setup_reply_queue
|
85
|
+
@reply_queue = commands.queue('', exclusive: true, auto_delete: true)
|
86
|
+
@reply_queue.bind(commands.exchange('josch_replies'), routing_key: @reply_queue.name)
|
87
|
+
|
88
|
+
reply_queue.subscribe do |delivery_info, properties, payload|
|
89
|
+
__id__ = properties[:correlation_id]
|
90
|
+
|
91
|
+
if __id__.nil?
|
92
|
+
puts "Received without __id__: #{delivery_info.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
93
|
+
}\n\n#{properties.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
94
|
+
}\n\n#{JSON.parse(payload).map { |k, v| "#{k}\t#{v}" }.join("\n")}" if @debug
|
95
|
+
|
96
|
+
elsif requests[__id__].nil?
|
97
|
+
puts "Received non-matching response, maybe previously timed out: \n\n#{delivery_info}\n\n#{properties}\n\n#{payload}\n."[..620].scan(/.{1,120}/).join(' '*30 + "\n") if @debug
|
98
|
+
else
|
99
|
+
# save the payload and send the signal to continue the execution of #command
|
100
|
+
# need to rescue the rare case, where lock and condition are destroyed right in parallel by timeout
|
101
|
+
begin
|
102
|
+
puts "Received result for #{__id__}" if @debug
|
103
|
+
requests[__id__][:response] = payload
|
104
|
+
requests[__id__][:lock].synchronize { requests[__id__][:condition].signal }
|
105
|
+
rescue nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
JoschClient = JoSchClient
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
__END__
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bunny'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Cotcube
|
8
|
+
module Helpers
|
9
|
+
class OrderClient
|
10
|
+
SECRETS_DEFAULT = {
|
11
|
+
'orderproxy_mq_proto' => 'http',
|
12
|
+
'orderproxy_mq_user' => 'guest',
|
13
|
+
'orderproxy_mq_password' => 'guest',
|
14
|
+
'orderproxy_mq_host' => 'localhost',
|
15
|
+
'orderproxy_mq_port' => '15672',
|
16
|
+
'orderproxy_mq_vhost' => '%2F'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
SECRETS = SECRETS_DEFAULT.merge(
|
20
|
+
lambda {
|
21
|
+
begin
|
22
|
+
YAML.safe_load(File.read(Cotcube::Helpers.init[:secrets_file]))
|
23
|
+
rescue StandardError
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
}.call
|
27
|
+
)
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@connection = Bunny.new(user: SECRETS['orderproxy_mq_user'],
|
31
|
+
password: SECRETS['orderproxy_mq_password'],
|
32
|
+
vhost: SECRETS['orderproxy_mq_vhost'])
|
33
|
+
@connection.start
|
34
|
+
|
35
|
+
@commands = connection.create_channel
|
36
|
+
@exchange = commands.direct('orderproxy_commands')
|
37
|
+
@requests = {}
|
38
|
+
@persistent = { depth: {}, realtimebars: {}, ticks: {} }
|
39
|
+
@debug = false
|
40
|
+
setup_reply_queue
|
41
|
+
end
|
42
|
+
|
43
|
+
# command acts a synchronizer: it sends the command and waits for the response
|
44
|
+
# otherwise times out --- the counterpart here is the subscription within
|
45
|
+
# setup_reply_queue
|
46
|
+
#
|
47
|
+
def command(command, timeout: 10)
|
48
|
+
command = { command: command.to_s } unless command.is_a? Hash
|
49
|
+
command[:timestamp] ||= (Time.now.to_f * 1000).to_i
|
50
|
+
request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
|
51
|
+
requests[request_id] = {
|
52
|
+
request: command,
|
53
|
+
id: request_id,
|
54
|
+
lock: Mutex.new,
|
55
|
+
condition: ConditionVariable.new
|
56
|
+
}
|
57
|
+
|
58
|
+
exchange.publish(command.to_json,
|
59
|
+
routing_key: 'orderproxy_commands',
|
60
|
+
correlation_id: request_id,
|
61
|
+
reply_to: reply_queue.name)
|
62
|
+
|
63
|
+
# wait for the signal to continue the execution
|
64
|
+
#
|
65
|
+
requests[request_id][:lock].synchronize do
|
66
|
+
requests[request_id][:condition].wait(requests[request_id][:lock], timeout)
|
67
|
+
end
|
68
|
+
|
69
|
+
# if we reached timeout, we will return nil, just for explicity
|
70
|
+
response = requests[request_id][:response].dup
|
71
|
+
requests.delete(request_id)
|
72
|
+
response
|
73
|
+
end
|
74
|
+
|
75
|
+
alias_method :send_command, :command
|
76
|
+
|
77
|
+
def stop
|
78
|
+
commands.close
|
79
|
+
connection.close
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_contracts(symbol:)
|
83
|
+
send_command({ command: :get_contracts, symbol: symbol })
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_accessor :response
|
87
|
+
attr_reader :lock, :condition
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
attr_reader :call_id, :connection, :requests, :persistent,
|
92
|
+
:commands, :server_queue_name, :reply_queue, :exchange
|
93
|
+
|
94
|
+
def setup_reply_queue
|
95
|
+
@reply_queue = commands.queue('', exclusive: true, auto_delete: true)
|
96
|
+
@reply_queue.bind(commands.exchange('orderproxy_replies'), routing_key: @reply_queue.name)
|
97
|
+
|
98
|
+
reply_queue.subscribe do |delivery_info, properties, payload|
|
99
|
+
__id__ = properties[:correlation_id]
|
100
|
+
|
101
|
+
if __id__.nil?
|
102
|
+
puts "Received without __id__: #{delivery_info.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
103
|
+
}\n\n#{properties.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
104
|
+
}\n\n#{JSON.parse(payload).map { |k, v| "#{k}\t#{v}" }.join("\n")}" if @debug
|
105
|
+
|
106
|
+
elsif requests[__id__].nil?
|
107
|
+
puts "Received non-matching response, maybe previously timed out: \n\n#{delivery_info}\n\n#{properties}\n\n#{payload}\n."[..620].scan(/.{1,120}/).join(' '*30 + "\n") if @debug
|
108
|
+
else
|
109
|
+
# save the payload and send the signal to continue the execution of #command
|
110
|
+
# need to rescue the rare case, where lock and condition are destroyed right in parallel by timeout
|
111
|
+
begin
|
112
|
+
puts "Received result for #{__id__}" if @debug
|
113
|
+
requests[__id__][:response] = payload
|
114
|
+
requests[__id__][:lock].synchronize { requests[__id__][:condition].signal }
|
115
|
+
rescue nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
__END__
|
125
|
+
begin
|
126
|
+
client = OrderClient.new
|
127
|
+
reply = client.send_command( { command: 'ping' } )
|
128
|
+
puts reply.nil? ? 'nil' : JSON.parse(reply)
|
129
|
+
ensure
|
130
|
+
client.stop
|
131
|
+
end
|
data/lib/cotcube-helpers.rb
CHANGED
@@ -21,6 +21,7 @@ require_relative 'cotcube-helpers/subpattern'
|
|
21
21
|
require_relative 'cotcube-helpers/parallelize'
|
22
22
|
require_relative 'cotcube-helpers/simple_output'
|
23
23
|
require_relative 'cotcube-helpers/simple_series_stats'
|
24
|
+
require_relative 'cotcube-helpers/deep_decode_datetime'
|
24
25
|
require_relative 'cotcube-helpers/constants'
|
25
26
|
require_relative 'cotcube-helpers/input'
|
26
27
|
require_relative 'cotcube-helpers/output'
|
@@ -52,4 +53,6 @@ module Cotcube
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
|
-
|
56
|
+
%w[ data cache order josch ].each do |part|
|
57
|
+
require_relative "cotcube-helpers/#{part}_client"
|
58
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/bin/bash
|
2
2
|
|
3
|
-
export rubyenv=/home/pepe/.rvm/environments/
|
3
|
+
export rubyenv=/home/pepe/.rvm/environments/ruby-2.7.5
|
4
4
|
|
5
5
|
. $rubyenv
|
6
6
|
cd /home/pepe/GEMS/${1}
|
@@ -10,6 +10,7 @@ ruby ${2} ${3} ${4} ${5} ${6}
|
|
10
10
|
|
11
11
|
|
12
12
|
exit
|
13
|
+
|
13
14
|
for testing run
|
14
15
|
env - `cat /home/pepe/bin/cron_ruby_wrapper.sh | tail -n 6` /bin/bash
|
15
16
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cotcube-helpers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin L. Tischendorf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '7'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: colorize
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -93,12 +93,16 @@ files:
|
|
93
93
|
- LICENSE.txt
|
94
94
|
- README.md
|
95
95
|
- VERSION
|
96
|
+
- bin/gitlog
|
97
|
+
- bin/uncommitted
|
96
98
|
- cotcube-helpers.gemspec
|
97
99
|
- lib/cotcube-helpers.rb
|
98
100
|
- lib/cotcube-helpers/array_ext.rb
|
101
|
+
- lib/cotcube-helpers/cache_client.rb
|
99
102
|
- lib/cotcube-helpers/constants.rb
|
100
103
|
- lib/cotcube-helpers/data_client.rb
|
101
104
|
- lib/cotcube-helpers/datetime_ext.rb
|
105
|
+
- lib/cotcube-helpers/deep_decode_datetime.rb
|
102
106
|
- lib/cotcube-helpers/enum_ext.rb
|
103
107
|
- lib/cotcube-helpers/expiration.rb
|
104
108
|
- lib/cotcube-helpers/get_id_set.rb
|
@@ -106,7 +110,9 @@ files:
|
|
106
110
|
- lib/cotcube-helpers/ib_contracts.rb
|
107
111
|
- lib/cotcube-helpers/init.rb
|
108
112
|
- lib/cotcube-helpers/input.rb
|
113
|
+
- lib/cotcube-helpers/josch_client.rb
|
109
114
|
- lib/cotcube-helpers/numeric_ext.rb
|
115
|
+
- lib/cotcube-helpers/order_client.rb
|
110
116
|
- lib/cotcube-helpers/orderclient.rb
|
111
117
|
- lib/cotcube-helpers/output.rb
|
112
118
|
- lib/cotcube-helpers/parallelize.rb
|