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 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: []