gossiperl_client 0.1.0
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 +15 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +151 -0
- data/gossiperl_client.gemspec +44 -0
- data/lib/gossiperl_client.rb +5 -0
- data/lib/gossiperl_client/encryption/aes256.rb +44 -0
- data/lib/gossiperl_client/headers.rb +46 -0
- data/lib/gossiperl_client/messaging.rb +120 -0
- data/lib/gossiperl_client/overlay_worker.rb +73 -0
- data/lib/gossiperl_client/requirements.rb +15 -0
- data/lib/gossiperl_client/resolution.rb +38 -0
- data/lib/gossiperl_client/serialization/serializer.rb +128 -0
- data/lib/gossiperl_client/state.rb +73 -0
- data/lib/gossiperl_client/supervisor.rb +81 -0
- data/lib/gossiperl_client/thrift/gossiperl_constants.rb +15 -0
- data/lib/gossiperl_client/thrift/gossiperl_types.rb +378 -0
- data/lib/gossiperl_client/transport/udp.rb +52 -0
- data/lib/gossiperl_client/util/validation.rb +37 -0
- data/lib/gossiperl_client/version.rb +9 -0
- data/tests/process_tests.rb +63 -0
- data/tests/thrift_tests.rb +45 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YzE0MjE3NzNmNjg1MWJhYzQwNTE2MDE1N2FmNzExMDc3ZTA3MDdiZQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NTBjODRjMzU1YjcxNjcyZGEyMTRkYThkMDYzNTY2OTczOTc2ZTI1MQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
Yzg0M2FhZGI4ODJmYjBiZDc2NzE0NzA3ZWYzODkzYzJlNDk2YzBiNDAzZmNm
|
10
|
+
MjI1YmFiZjQ3MTc5OTQ0NGEyZGYwY2Q0NjkxMGZiYTQzZTRiNTM1YmVkOTEw
|
11
|
+
NjljNzkzOTM1N2VlMThiZjdhYzYyZjFjM2JlMjFhZTY2Mjc4Yzg=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YmRiY2ZhOGU1MjExMTk3MzI3MzE1ZDc4MjQ3NjA2NGZmZmNiYzZmMjBmNDRl
|
14
|
+
NWQ5YWZkNzQzMTJhOGE1MzFhODAxYmE0YzY2NzBhOWIxZjQ4NTRhZjY4OTU2
|
15
|
+
ZjYzY2ZjODdlNjY1MDJkOTMxM2FmNzU5Y2Y2ZWMyYWZiYmZmZTA=
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Radoslaw Gruchalski <radek@gruchalski.com>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# Ruby gossiperl client
|
2
|
+
|
3
|
+
Ruby [gossiperl](https://github.com/radekg/gossiperl) client library.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
In your `Gemfile`:
|
8
|
+
|
9
|
+
gem 'gossiperl_client', :git => 'https://github.com/radekg/gossiperl-client-ruby.git'
|
10
|
+
|
11
|
+
## Running
|
12
|
+
|
13
|
+
require 'gossiperl_client'
|
14
|
+
supervisor = ::Gossiperl::Client::Supervisor.new
|
15
|
+
|
16
|
+
## Connecting to an overlay
|
17
|
+
|
18
|
+
supervisor.connect( :overlay_name => :your_overlay_name
|
19
|
+
:overlay_port => 6666,
|
20
|
+
:client_port => 54321,
|
21
|
+
:client_name => :your_client_name,
|
22
|
+
:client_secret => :your_client_secret,
|
23
|
+
:symkey => :symmetric_key )
|
24
|
+
|
25
|
+
It's also possible to connect with a block:
|
26
|
+
|
27
|
+
supervisor.connect( ... ) do |event|
|
28
|
+
if event[:event] == :connected
|
29
|
+
self.logger.info "Connected to overlay #{event[:options][:overlay_name]}..."
|
30
|
+
elsif event[:event] == :disconnected
|
31
|
+
self.logger.info "Disconnected from overlay #{event[:options][:overlay_name]}..."
|
32
|
+
elsif event[:event] == :subscribed
|
33
|
+
self.logger.info "Received subscription confirmation for #{event[:details][:types]}"
|
34
|
+
elsif event[:event] == :unsubscribed
|
35
|
+
self.logger.info "Received unsubscription confirmation for #{event[:details][:types]}"
|
36
|
+
elsif event[:event] == :event
|
37
|
+
self.logger.info "Received member related event #{event[:details][:type]} for member #{event[:details][:member]}."
|
38
|
+
elsif event[:event] == :forwarded_ack
|
39
|
+
self.logger.info "Received confirmation of forwarded message. Message ID: #{event[:details][:reply_id]}"
|
40
|
+
elsif event[:event] == :forwarded
|
41
|
+
self.logger.info "Received forwarded digest #{event[:digest]} of type #{event[:digest_type]}"
|
42
|
+
elsif event[:event] == :failed
|
43
|
+
self.logger.info "Received an error from the client. Reason: #{event[:error]}."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
A client may be connected to multiple overlays.
|
48
|
+
|
49
|
+
## Subscribing / unsubscribing
|
50
|
+
|
51
|
+
Subscribing:
|
52
|
+
|
53
|
+
supervisor.subscribe( :overlay_name, [ :event_1, :event_2, ... ] )
|
54
|
+
|
55
|
+
Unsubscribing:
|
56
|
+
|
57
|
+
supervisor.unsubscribe( :overlay_name, [ :event_1, :event_2, ... ] )
|
58
|
+
|
59
|
+
Or in a block:
|
60
|
+
|
61
|
+
self.subscribe( [ :event_1, :event_2, ... ] )
|
62
|
+
self.unsubscribe( [ :event_1, :event_2, ... ] )
|
63
|
+
|
64
|
+
## Disconnecting from an overlay:
|
65
|
+
|
66
|
+
supervisor.disconnect( :overlay_name )
|
67
|
+
|
68
|
+
Or in a block:
|
69
|
+
|
70
|
+
self.stop
|
71
|
+
|
72
|
+
This will attempt a graceful exit from an overlay.
|
73
|
+
|
74
|
+
## Additional operations
|
75
|
+
|
76
|
+
### Checking current client state
|
77
|
+
|
78
|
+
supervisor.state( :overlay_name )
|
79
|
+
|
80
|
+
Or in a block:
|
81
|
+
|
82
|
+
self.current_state
|
83
|
+
|
84
|
+
### Get the list of current subscriptions
|
85
|
+
|
86
|
+
supervisor.subscriptions( :overlay_name )
|
87
|
+
|
88
|
+
Or in a block:
|
89
|
+
|
90
|
+
self.state.subscriptions
|
91
|
+
|
92
|
+
### Sending arbitrary digests
|
93
|
+
|
94
|
+
|
95
|
+
supervisor.send( :overlay_name, :digestType, {
|
96
|
+
:property => { :value => <value>, :type => <thrift-type-as-string>, :field_id => <field-order> }
|
97
|
+
} )
|
98
|
+
|
99
|
+
Or in a block:
|
100
|
+
|
101
|
+
self.send( :digestType, {
|
102
|
+
:property => { :value => <value>, :type => <thrift-type-as-string>, :field_id => <field-order> }
|
103
|
+
} )
|
104
|
+
|
105
|
+
Where `:type` is one of the Thrift types:
|
106
|
+
|
107
|
+
- `:stop`
|
108
|
+
- `:void`
|
109
|
+
- `:bool`
|
110
|
+
- `:byte`
|
111
|
+
- `:double`
|
112
|
+
- `:i16`
|
113
|
+
- `:i32`
|
114
|
+
- `:i64`
|
115
|
+
- `:string`
|
116
|
+
- `:struct`
|
117
|
+
- `:map`
|
118
|
+
- `:set`
|
119
|
+
- `:list`
|
120
|
+
|
121
|
+
And `:field_id` is a Thrift field ID.
|
122
|
+
|
123
|
+
## Running tests
|
124
|
+
|
125
|
+
shindont tests
|
126
|
+
|
127
|
+
Tests assume an overlay with the details specified in the `tests/process_tests.rb` running.
|
128
|
+
|
129
|
+
## License
|
130
|
+
|
131
|
+
The MIT License (MIT)
|
132
|
+
|
133
|
+
Copyright (c) 2014 Radoslaw Gruchalski <radek@gruchalski.com>
|
134
|
+
|
135
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
136
|
+
of this software and associated documentation files (the "Software"), to deal
|
137
|
+
in the Software without restriction, including without limitation the rights
|
138
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
139
|
+
copies of the Software, and to permit persons to whom the Software is
|
140
|
+
furnished to do so, subject to the following conditions:
|
141
|
+
|
142
|
+
The above copyright notice and this permission notice shall be included in
|
143
|
+
all copies or substantial portions of the Software.
|
144
|
+
|
145
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
146
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
147
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
148
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
149
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
150
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
151
|
+
THE SOFTWARE.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "gossiperl_client/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gossiperl_client"
|
7
|
+
s.version = Gossiperl::Client::Version::VERSION
|
8
|
+
s.has_rdoc = false
|
9
|
+
s.summary = "Gossiperl Ruby client"
|
10
|
+
s.description = "Work with gossiperl from Ruby."
|
11
|
+
s.authors = ["Rad Gruchalski"]
|
12
|
+
s.email = ["radek@gruchalski.com"]
|
13
|
+
s.homepage = "https://github.com/radekg/gossiperl-client-ruby"
|
14
|
+
s.require_paths = %w[lib]
|
15
|
+
|
16
|
+
s.add_dependency('thrift', '>=0.9.2.0')
|
17
|
+
s.add_development_dependency('shindo')
|
18
|
+
|
19
|
+
s.files = %w[
|
20
|
+
Gemfile
|
21
|
+
README.md
|
22
|
+
LICENSE
|
23
|
+
lib/gossiperl_client.rb
|
24
|
+
lib/gossiperl_client/encryption/aes256.rb
|
25
|
+
lib/gossiperl_client/serialization/serializer.rb
|
26
|
+
lib/gossiperl_client/thrift/gossiperl_constants.rb
|
27
|
+
lib/gossiperl_client/thrift/gossiperl_types.rb
|
28
|
+
lib/gossiperl_client/transport/udp.rb
|
29
|
+
lib/gossiperl_client/util/validation.rb
|
30
|
+
lib/gossiperl_client/headers.rb
|
31
|
+
lib/gossiperl_client/messaging.rb
|
32
|
+
lib/gossiperl_client/overlay_worker.rb
|
33
|
+
lib/gossiperl_client/requirements.rb
|
34
|
+
lib/gossiperl_client/resolution.rb
|
35
|
+
lib/gossiperl_client/state.rb
|
36
|
+
lib/gossiperl_client/supervisor.rb
|
37
|
+
lib/gossiperl_client/version.rb
|
38
|
+
gossiperl_client.gemspec
|
39
|
+
tests/process_tests.rb
|
40
|
+
tests/thrift_tests.rb
|
41
|
+
]
|
42
|
+
s.test_files = s.files.select { |path| path =~ /^[tests]\/.*_[tests]\.rb/ }
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
module Gossiperl
|
3
|
+
module Client
|
4
|
+
module Encryption
|
5
|
+
class Aes256 < Gossiperl::Client::Resolution
|
6
|
+
|
7
|
+
field :key, Object
|
8
|
+
|
9
|
+
def initialize key_in
|
10
|
+
# setup key:
|
11
|
+
self.key = ::Digest::SHA256.digest(key_in)
|
12
|
+
end
|
13
|
+
|
14
|
+
def algorithm
|
15
|
+
'AES-256-CBC'
|
16
|
+
end
|
17
|
+
|
18
|
+
def encrypt data
|
19
|
+
random_iv = OpenSSL::Cipher::Cipher.new(algorithm).random_iv
|
20
|
+
aes = ::OpenSSL::Cipher::Cipher.new(algorithm)
|
21
|
+
aes.encrypt
|
22
|
+
aes.key = self.key
|
23
|
+
aes.iv = random_iv
|
24
|
+
cipher = aes.update(data)
|
25
|
+
cipher << aes.final
|
26
|
+
random_iv + cipher
|
27
|
+
end
|
28
|
+
|
29
|
+
def decrypt cipher
|
30
|
+
iv = cipher[0...16]
|
31
|
+
cipher_data = cipher[16..-1]
|
32
|
+
decode_cipher = ::OpenSSL::Cipher::Cipher.new(algorithm)
|
33
|
+
decode_cipher.decrypt
|
34
|
+
decode_cipher.key = self.key
|
35
|
+
decode_cipher.padding = 0
|
36
|
+
decode_cipher.iv = iv
|
37
|
+
plain = decode_cipher.update(cipher_data)
|
38
|
+
plain << decode_cipher.final
|
39
|
+
plain
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
module Gossiperl
|
3
|
+
module Client
|
4
|
+
|
5
|
+
class Resolution; end
|
6
|
+
|
7
|
+
module Encryption
|
8
|
+
class Aes256 < Gossiperl::Client::Resolution; end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Serialization
|
12
|
+
class Serializer; end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Thrift
|
16
|
+
class DigestEnvelope; end
|
17
|
+
class DigestForwardedAck; end
|
18
|
+
class DigestError; end
|
19
|
+
class DigestExit; end
|
20
|
+
class DigestMember; end
|
21
|
+
class DigestSubscription; end
|
22
|
+
class Digest; end
|
23
|
+
class DigestAck; end
|
24
|
+
class DigestSubscriptions; end
|
25
|
+
class DigestSubscribe; end
|
26
|
+
class DigestSubscribeAck; end
|
27
|
+
class DigestUnsubscribe; end
|
28
|
+
class DigestUnsubscribeAck; end
|
29
|
+
class DigestEvent; end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Transport
|
33
|
+
class Udp < Gossiperl::Client::Resolution; end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Util
|
37
|
+
class Validation; end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Messaging < Gossiperl::Client::Resolution; end
|
41
|
+
class OverlayWorker < Gossiperl::Client::Resolution; end
|
42
|
+
class State < Gossiperl::Client::Resolution; end
|
43
|
+
class Supervisor < Gossiperl::Client::Resolution; end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
module Gossiperl
|
3
|
+
module Client
|
4
|
+
class Messaging < Gossiperl::Client::Resolution
|
5
|
+
|
6
|
+
field :worker, Gossiperl::Client::OverlayWorker
|
7
|
+
field :transport, Gossiperl::Client::Transport::Udp
|
8
|
+
|
9
|
+
def initialize worker, &block
|
10
|
+
self.worker = worker
|
11
|
+
@callback_block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_callback_block
|
15
|
+
@callback_block
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
self.transport = Gossiperl::Client::Transport::Udp.new( self.worker )
|
20
|
+
if self.worker.options.has_key?(:thrift_window)
|
21
|
+
self.transport.recv_buf_size = self.worker.options[:thrift_window]
|
22
|
+
end
|
23
|
+
Thread.new(self) do |msg|
|
24
|
+
msg.transport.handle do |data|
|
25
|
+
if data.kind_of? Hash
|
26
|
+
if data.has_key?(:error)
|
27
|
+
msg.worker.process_event( { :event => :failed,
|
28
|
+
:error => data[:error] } )
|
29
|
+
elsif data.has_key?(:forward)
|
30
|
+
msg.worker.process_event( { :event => :forwarded,
|
31
|
+
:digest => data[:envelope],
|
32
|
+
:digest_type => data[:type] } )
|
33
|
+
msg.digest_forwarded_ack data[:envelope].id
|
34
|
+
else
|
35
|
+
msg.worker.process_event( { :event => :failed,
|
36
|
+
:error => { :unsupported_hash_response => data } } )
|
37
|
+
end
|
38
|
+
else
|
39
|
+
if data.is_a?( Gossiperl::Client::Thrift::Digest )
|
40
|
+
msg.digest_ack data
|
41
|
+
elsif data.is_a?( Gossiperl::Client::Thrift::DigestAck )
|
42
|
+
msg.worker.state.receive data
|
43
|
+
elsif data.is_a?( Gossiperl::Client::Thrift::DigestEvent )
|
44
|
+
msg.worker.process_event( { :event => :event,
|
45
|
+
:details => { :type => data.event_type,
|
46
|
+
:member => data.event_object,
|
47
|
+
:heartbeat => data.heartbeat } } )
|
48
|
+
elsif data.is_a?( Gossiperl::Client::Thrift::DigestSubscribeAck )
|
49
|
+
msg.worker.process_event( { :event => :subscribed,
|
50
|
+
:details => { :types => data.event_types.map{|item| item.to_sym},
|
51
|
+
:heartbeat => data.heartbeat } } )
|
52
|
+
elsif data.is_a?( Gossiperl::Client::Thrift::DigestUnsubscribeAck )
|
53
|
+
msg.worker.process_event( { :event => :unsubscribed,
|
54
|
+
:details => { :types => data.event_types.map{|item| item.to_sym},
|
55
|
+
:heartbeat => data.heartbeat } } )
|
56
|
+
elsif data.is_a?( Gossiperl::Client::Thrift::DigestForwardedAck )
|
57
|
+
msg.worker.process_event( { :event => :forwarded_ack,
|
58
|
+
:details => { :reply_id => data.reply_id } } )
|
59
|
+
else
|
60
|
+
msg.worker.process_event( { :event => :failed,
|
61
|
+
:error => { :unsupported_digest => data } } )
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def send digest
|
69
|
+
self.transport.send digest
|
70
|
+
end
|
71
|
+
|
72
|
+
def digest_ack digest
|
73
|
+
ack = ::Gossiperl::Client::Thrift::DigestAck.new
|
74
|
+
ack.name = self.worker.options[:client_name].to_s
|
75
|
+
ack.heartbeat = Time.now.to_i
|
76
|
+
ack.reply_id = digest.id
|
77
|
+
ack.membership = []
|
78
|
+
self.send ack
|
79
|
+
end
|
80
|
+
|
81
|
+
def digest_forwarded_ack digest_id
|
82
|
+
ack = ::Gossiperl::Client::Thrift::DigestForwardedAck.new
|
83
|
+
ack.name = self.worker.options[:client_name].to_s
|
84
|
+
ack.secret = self.worker.options[:client_secret].to_s
|
85
|
+
ack.reply_id = digest_id
|
86
|
+
self.send ack
|
87
|
+
end
|
88
|
+
|
89
|
+
def digest_subscribe event_types
|
90
|
+
digest = ::Gossiperl::Client::Thrift::DigestSubscribe.new
|
91
|
+
digest.name = self.worker.options[:client_name].to_s
|
92
|
+
digest.secret = self.worker.options[:client_secret].to_s
|
93
|
+
digest.id = SecureRandom.uuid.to_s
|
94
|
+
digest.heartbeat = Time.now.to_i
|
95
|
+
digest.event_types = event_types.map{|item| item.to_s}
|
96
|
+
self.send digest
|
97
|
+
end
|
98
|
+
|
99
|
+
def digest_unsubscribe event_types
|
100
|
+
digest = ::Gossiperl::Client::Thrift::DigestUnsubscribe.new
|
101
|
+
digest.name = self.worker.options[:client_name].to_s
|
102
|
+
digest.secret = self.worker.options[:client_secret].to_s
|
103
|
+
digest.id = SecureRandom.uuid.to_s
|
104
|
+
digest.heartbeat = Time.now.to_i
|
105
|
+
digest.event_types = event_types.map{|item| item.to_s}
|
106
|
+
self.send digest
|
107
|
+
end
|
108
|
+
|
109
|
+
def digest_exit
|
110
|
+
digest = ::Gossiperl::Client::Thrift::DigestExit.new
|
111
|
+
digest.name = self.worker.options[:client_name].to_s
|
112
|
+
digest.heartbeat = Time.now.to_i
|
113
|
+
digest.secret = self.worker.options[:client_secret].to_s
|
114
|
+
self.send digest
|
115
|
+
self.worker.working = false
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|