pubcontrol 0.0.2
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.
- checksums.yaml +7 -0
- data/lib/format.rb +15 -0
- data/lib/item.rb +32 -0
- data/lib/pccbhandler.rb +30 -0
- data/lib/pubcontrol.rb +196 -0
- data/lib/pubcontrolset.rb +71 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 852c3861e8aad560d515e5c32f117c48956e22a3
|
4
|
+
data.tar.gz: b63b17e203c3aa21f12371103d44a4b4ea57835e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a470816b50c288d8f5ae5eee40c002bdcea7031f42afb2c843953e559da762190e9915c2fe587c88d35a7ceaf1ecf6593c68e11ac49bf21e146609f2daf44a7
|
7
|
+
data.tar.gz: ec30846c776afae6ea6a34c11fcbb10fc781e5360b385509ed2a8742ece70f94d340270cec2b353560cf25c2a725879ceb2051663d5b6ce078e587932bd07d1f
|
data/lib/format.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# format.rb
|
2
|
+
# ~~~~~~~~~
|
3
|
+
# This module implements the Format class.
|
4
|
+
# :copyright: (c) 2014 by Konstantin Bokarius.
|
5
|
+
# :license: MIT, see LICENSE for more details.
|
6
|
+
|
7
|
+
class Format
|
8
|
+
def name
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def export
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
end
|
data/lib/item.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# item.rb
|
2
|
+
# ~~~~~~~~~
|
3
|
+
# This module implements the Item class.
|
4
|
+
# :copyright: (c) 2014 by Konstantin Bokarius.
|
5
|
+
# :license: MIT, see LICENSE for more details.
|
6
|
+
|
7
|
+
require_relative 'format.rb'
|
8
|
+
|
9
|
+
class Item
|
10
|
+
def initialize(formats, id=nil, prev_id=nil)
|
11
|
+
@id = id
|
12
|
+
@prev_id = prev_id
|
13
|
+
if formats.is_a? Format
|
14
|
+
formats = [formats]
|
15
|
+
end
|
16
|
+
@formats = formats
|
17
|
+
end
|
18
|
+
|
19
|
+
def export
|
20
|
+
out = Hash.new
|
21
|
+
if !@id.nil?
|
22
|
+
out['id'] = @id
|
23
|
+
end
|
24
|
+
if !@prev_id.nil?
|
25
|
+
out['prev-id'] = @prev_id
|
26
|
+
end
|
27
|
+
@formats.each do |format|
|
28
|
+
out[format.name] = format.export
|
29
|
+
end
|
30
|
+
return out
|
31
|
+
end
|
32
|
+
end
|
data/lib/pccbhandler.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# format.rb
|
2
|
+
# ~~~~~~~~~
|
3
|
+
# This module implements the PubControlCallbackHandler class.
|
4
|
+
# :copyright: (c) 2014 by Konstantin Bokarius.
|
5
|
+
# :license: MIT, see LICENSE for more details.
|
6
|
+
|
7
|
+
class PubControlCallbackHandler
|
8
|
+
def initialize(num_calls, callback)
|
9
|
+
@num_calls = num_calls
|
10
|
+
@callback = callback
|
11
|
+
@success = true
|
12
|
+
@first_error_message = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def handler(success, message)
|
16
|
+
if !success and @success
|
17
|
+
@success = false
|
18
|
+
@first_error_message = message
|
19
|
+
end
|
20
|
+
@num_calls -= 1
|
21
|
+
if @num_calls <= 0
|
22
|
+
@callback.call(@success, @first_error_message)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# TODO: how to get handler symbol without this method?
|
27
|
+
def handler_method_symbol
|
28
|
+
return method(:handler)
|
29
|
+
end
|
30
|
+
end
|
data/lib/pubcontrol.rb
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
# pubcontrol.rb
|
2
|
+
# ~~~~~~~~~
|
3
|
+
# This module implements the PubControl class.
|
4
|
+
# :copyright: (c) 2014 by Konstantin Bokarius.
|
5
|
+
# :license: MIT, see LICENSE for more details.
|
6
|
+
|
7
|
+
require 'algorithms'
|
8
|
+
require 'thread'
|
9
|
+
require 'base64'
|
10
|
+
require 'jwt'
|
11
|
+
require 'json'
|
12
|
+
require 'net/http'
|
13
|
+
require_relative 'item.rb'
|
14
|
+
require_relative 'format.rb'
|
15
|
+
require_relative 'pubcontrolset.rb'
|
16
|
+
|
17
|
+
class PubControl
|
18
|
+
attr_accessor :req_queue
|
19
|
+
|
20
|
+
def initialize(uri)
|
21
|
+
@uri = uri
|
22
|
+
@lock = Mutex.new
|
23
|
+
@thread = nil
|
24
|
+
@thread_cond = nil
|
25
|
+
@thread_mutex = nil
|
26
|
+
@req_queue = Containers::Deque.new
|
27
|
+
@auth_basic_user = nil
|
28
|
+
@auth_basic_pass = nil
|
29
|
+
@auth_jwt_claim = nil
|
30
|
+
@auth_jwt_key = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_auth_basic(username, password)
|
34
|
+
@lock.synchronize do
|
35
|
+
@auth_basic_user = username
|
36
|
+
@auth_basic_pass = password
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_auth_jwt(claim, key)
|
41
|
+
@lock.synchronize do
|
42
|
+
@auth_jwt_claim = claim
|
43
|
+
@auth_jwt_key = key
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def publish(channel, item)
|
48
|
+
export = item.export
|
49
|
+
export['channel'] = channel
|
50
|
+
uri = nil
|
51
|
+
auth = nil
|
52
|
+
@lock.synchronize do
|
53
|
+
uri = @uri
|
54
|
+
auth = gen_auth_header
|
55
|
+
end
|
56
|
+
PubControl.pubcall(uri, auth, [export])
|
57
|
+
end
|
58
|
+
|
59
|
+
def publish_async(channel, item, callback=nil)
|
60
|
+
export = item.export
|
61
|
+
export['channel'] = channel
|
62
|
+
uri = nil
|
63
|
+
auth = nil
|
64
|
+
@lock.synchronize do
|
65
|
+
uri = @uri
|
66
|
+
auth = gen_auth_header
|
67
|
+
ensure_thread
|
68
|
+
end
|
69
|
+
queue_req(['pub', uri, auth, export, callback])
|
70
|
+
end
|
71
|
+
|
72
|
+
def finish
|
73
|
+
@lock.synchronize do
|
74
|
+
if !@thread.nil?
|
75
|
+
queue_req(['stop'])
|
76
|
+
@thread.join
|
77
|
+
@thread = nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def pubworker
|
83
|
+
quit = false
|
84
|
+
while !quit do
|
85
|
+
@thread_mutex.lock
|
86
|
+
if @req_queue.length == 0
|
87
|
+
@thread_cond.wait(@thread_mutex)
|
88
|
+
if @req_queue.length == 0
|
89
|
+
@thread_mutex.unlock
|
90
|
+
next
|
91
|
+
end
|
92
|
+
end
|
93
|
+
reqs = Array.new
|
94
|
+
while @req_queue.length > 0 and reqs.length < 10 do
|
95
|
+
m = @req_queue.pop_front
|
96
|
+
if m[0] == 'stop'
|
97
|
+
quit = true
|
98
|
+
break
|
99
|
+
end
|
100
|
+
reqs.push([m[1], m[2], m[3], m[4]])
|
101
|
+
end
|
102
|
+
@thread_mutex.unlock
|
103
|
+
if reqs.length > 0
|
104
|
+
PubControl.pubbatch(reqs)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.pubcall(uri, auth_header, items)
|
110
|
+
uri = URI(uri + '/publish/')
|
111
|
+
content = Hash.new
|
112
|
+
content['items'] = items
|
113
|
+
# REVIEW: POST implementation
|
114
|
+
request = Net::HTTP::Post.new(uri.path)
|
115
|
+
# REVIEW: Ruby strings are unicode by default
|
116
|
+
# is forcing the encoding to UTF-8 necessary?
|
117
|
+
request.body = content.to_json.force_encoding('UTF-8')
|
118
|
+
if !auth_header.nil?
|
119
|
+
request['Authorization'] = auth_header
|
120
|
+
end
|
121
|
+
request['Content-Type'] = 'application/json'
|
122
|
+
response = Net::HTTP.start(uri.host, use_ssl: true) do |http|
|
123
|
+
http.request(request)
|
124
|
+
end
|
125
|
+
# REVIEW: HTTPSuccess does not include 3xx status codes.
|
126
|
+
if !response.kind_of? Net::HTTPSuccess
|
127
|
+
raise 'failed to publish: ' + response.class.to_s + ' ' +
|
128
|
+
response.message
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.pubbatch(reqs)
|
133
|
+
raise 'reqs length == 0' unless reqs.length > 0
|
134
|
+
uri = reqs[0][0]
|
135
|
+
auth_header = reqs[0][1]
|
136
|
+
items = Array.new
|
137
|
+
callbacks = Array.new
|
138
|
+
reqs.each do |req|
|
139
|
+
items.push(req[2])
|
140
|
+
callbacks.push(req[3])
|
141
|
+
end
|
142
|
+
begin
|
143
|
+
PubControl.pubcall(uri, auth_header, items)
|
144
|
+
result = [true, '']
|
145
|
+
rescue => e
|
146
|
+
result = [false, e.message]
|
147
|
+
end
|
148
|
+
callbacks.each do |callback|
|
149
|
+
if !callback.nil?
|
150
|
+
callback.call(result[0], result[1])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.timestamp_utcnow
|
156
|
+
# REVIEW: gmtime Ruby implementation
|
157
|
+
return Time.now.utc.to_i
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def gen_auth_header
|
163
|
+
if !@auth_basic_user.nil?
|
164
|
+
return 'Basic ' + Base64.encode64(
|
165
|
+
'#{@auth_basic_user}:#{@auth_basic_pass}')
|
166
|
+
elsif !@auth_jwt_claim.nil?
|
167
|
+
if !@auth_jwt_claim.has_key?('exp')
|
168
|
+
claim = @auth_jwt_claim.clone
|
169
|
+
claim['exp'] = PubControl.timestamp_utcnow + 3600
|
170
|
+
else
|
171
|
+
claim = @auth_jwt_claim
|
172
|
+
end
|
173
|
+
return 'Bearer ' + JWT.encode(claim, @auth_jwt_key)
|
174
|
+
else
|
175
|
+
return None
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def ensure_thread
|
180
|
+
if @thread.nil?
|
181
|
+
@thread_cond = ConditionVariable.new
|
182
|
+
@thread_mutex = Mutex.new
|
183
|
+
@thread = Thread.new { pubworker }
|
184
|
+
# REVIEW: Ruby threads are daemonic by default
|
185
|
+
#@thread.daemon = true
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def queue_req(req)
|
190
|
+
# REVIEW: thread condition implementation
|
191
|
+
@thread_mutex.lock
|
192
|
+
@req_queue.push_back(req)
|
193
|
+
@thread_cond.signal
|
194
|
+
@thread_mutex.unlock
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# pubcontrolset.rb
|
2
|
+
# ~~~~~~~~~
|
3
|
+
# This module implements the PubControlSet class.
|
4
|
+
# :copyright: (c) 2014 by Konstantin Bokarius.
|
5
|
+
# :license: MIT, see LICENSE for more details.
|
6
|
+
|
7
|
+
require_relative 'pubcontrol.rb'
|
8
|
+
require_relative 'pccbhandler.rb'
|
9
|
+
|
10
|
+
class PubControlSet
|
11
|
+
def initialize
|
12
|
+
@pubs = Array.new
|
13
|
+
at_exit { finish }
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear
|
17
|
+
@pubs = Array.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def add(pub)
|
21
|
+
@pubs.push(pub)
|
22
|
+
end
|
23
|
+
|
24
|
+
def apply_config(config)
|
25
|
+
config.each do |entry|
|
26
|
+
pub = PubControl.new(entry['uri'])
|
27
|
+
if entry.key?('iss')
|
28
|
+
pub.set_auth_jwt({'iss' => entry['iss']}, entry['key'])
|
29
|
+
end
|
30
|
+
@pubs.push(pub)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def apply_grip_config(config)
|
35
|
+
config.each do |entry|
|
36
|
+
if !entry.key?('control_uri')
|
37
|
+
next
|
38
|
+
end
|
39
|
+
pub = PubControl.new(entry['control_uri'])
|
40
|
+
if !entry.key?('control_iss')
|
41
|
+
pub.set_auth_jwt({'iss' => entry['control_iss']}, entry['key'])
|
42
|
+
end
|
43
|
+
@pubs.push(pub)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def publish(channel, item, blocking=false, callback=nil)
|
48
|
+
if blocking
|
49
|
+
@pubs.each do |pub|
|
50
|
+
pub.publish(channel, item)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
cb = nil
|
54
|
+
if !callback.nil?
|
55
|
+
cb = PubControlCallbackHandler.new(@pubs.length, callback).
|
56
|
+
handler_method_symbol
|
57
|
+
end
|
58
|
+
@pubs.each do |pub|
|
59
|
+
pub.publish_async(channel, item, cb)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def finish
|
67
|
+
@pubs.each do |pub|
|
68
|
+
pub.finish
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pubcontrol
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Konstantin Bokarius
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-02 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A Ruby implementation of the EPCP protocol.
|
14
|
+
email: bokarius@comcast.net
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/format.rb
|
20
|
+
- lib/item.rb
|
21
|
+
- lib/pccbhandler.rb
|
22
|
+
- lib/pubcontrol.rb
|
23
|
+
- lib/pubcontrolset.rb
|
24
|
+
homepage: http://rubygems.org/gems/pubcontrol
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.2.2
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Ruby EPCP library
|
48
|
+
test_files: []
|