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