pubcontrol 0.0.2

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