akane 0.1.0 → 0.2.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 +4 -4
- data/akane.example.yml +21 -0
- data/akane.gemspec +2 -0
- data/lib/akane/config.rb +10 -0
- data/lib/akane/manager.rb +52 -2
- data/lib/akane/receivers/stream.rb +19 -12
- data/lib/akane/recorder.rb +49 -11
- data/lib/akane/storages/abstract_storage.rb +17 -0
- data/lib/akane/storages/elasticsearch.rb +4 -0
- data/lib/akane/storages/file.rb +4 -0
- data/lib/akane/version.rb +1 -1
- data/spec/manager_spec.rb +3 -2
- data/spec/recorder_spec.rb +26 -8
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de30517cd16b9dd52bf5628602e20e22dfcb594a
|
4
|
+
data.tar.gz: 509875d93f4c1bcebfb950f289b206d69916f2ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98ec5786e6587eac236b868309b85fc9a8d1fc445b761f927e1085c04629fb7534105bee28bec99796af86b7aa2ba44ea9e87fb5f6a738370b5ca97e36a91038
|
7
|
+
data.tar.gz: 3c6ab75d276a089ce1a0486dfb608c3a09f029ece9096a94a98caf2927447ef275c9f64338e7f707ae96dd273da997b18e0d1b9cea35935e4d8e59c6720c4d2a
|
data/akane.example.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
consumer:
|
3
|
+
token: CONSUMER_TOKEN
|
4
|
+
secret: CONSUMER_SECRET
|
5
|
+
accounts:
|
6
|
+
your_account_name1:
|
7
|
+
token: ACCESS_TOKEN1
|
8
|
+
secret: SECRET_TOKEN1
|
9
|
+
your_account_name2:
|
10
|
+
token: ACCESS_TOKEN2
|
11
|
+
secret: SECRET_TOKEN2
|
12
|
+
storages:
|
13
|
+
- stdout
|
14
|
+
- file:
|
15
|
+
dir: /tmp/akane
|
16
|
+
sync_io: false
|
17
|
+
- elasticsearch:
|
18
|
+
host: localhost
|
19
|
+
index: akane
|
20
|
+
kuromoji: false
|
21
|
+
enable_es_log: false
|
data/akane.gemspec
CHANGED
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_dependency "elasticsearch", "~> 0.4.1"
|
22
22
|
spec.add_dependency "twitter", "~> 5.5.1"
|
23
23
|
spec.add_dependency "oauth", ">= 0.4.7"
|
24
|
+
spec.add_dependency "sigdump"
|
25
|
+
|
24
26
|
|
25
27
|
spec.add_development_dependency "bundler"
|
26
28
|
spec.add_development_dependency "rake"
|
data/lib/akane/config.rb
CHANGED
data/lib/akane/manager.rb
CHANGED
@@ -35,7 +35,7 @@ module Akane
|
|
35
35
|
[[definition, {}]]
|
36
36
|
end
|
37
37
|
end.map do |kind, config|
|
38
|
-
@logger.info "Preparing...
|
38
|
+
@logger.info "Preparing... storage - #{kind}"
|
39
39
|
require "akane/storages/#{kind}"
|
40
40
|
Akane::Storages.const_get(kind.gsub(/(?:\A|_)(.)/) { $1.upcase }).new(
|
41
41
|
config: config,
|
@@ -43,7 +43,11 @@ module Akane
|
|
43
43
|
)
|
44
44
|
end
|
45
45
|
|
46
|
-
@recorder = Akane::Recorder.new(
|
46
|
+
@recorder = Akane::Recorder.new(
|
47
|
+
@storages,
|
48
|
+
timeout: @config["timeout"] || 20,
|
49
|
+
logger: @config.logger
|
50
|
+
)
|
47
51
|
|
48
52
|
@logger.info "Prepared with #{@storages.size} storage(s) and #{@receivers.size} receiver(s)"
|
49
53
|
end
|
@@ -51,8 +55,38 @@ module Akane
|
|
51
55
|
def start
|
52
56
|
@logger.info "Starting receivers..."
|
53
57
|
@receivers.each(&:start)
|
58
|
+
|
59
|
+
@logger.info "Assigning signal handlers..."
|
60
|
+
handle_signals
|
61
|
+
|
54
62
|
@logger.info "Starting recorder..."
|
55
63
|
@recorder.run
|
64
|
+
|
65
|
+
@logger.info "Recorder stopped. Waiting for storages..."
|
66
|
+
stop_storages
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_signals
|
70
|
+
@terminating = false
|
71
|
+
|
72
|
+
begin
|
73
|
+
require 'sigdump/setup'
|
74
|
+
rescue LoadError
|
75
|
+
end
|
76
|
+
|
77
|
+
on_interrupt = proc do
|
78
|
+
if @terminating
|
79
|
+
@config.log_direct "Terminating forcely..."
|
80
|
+
exit
|
81
|
+
else
|
82
|
+
@terminating = true
|
83
|
+
@config.log_direct "Gracefully stopping..."
|
84
|
+
@recorder.stop!
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
trap(:INT, on_interrupt)
|
89
|
+
trap(:TERM, on_interrupt)
|
56
90
|
end
|
57
91
|
|
58
92
|
def run
|
@@ -62,6 +96,22 @@ module Akane
|
|
62
96
|
start()
|
63
97
|
end
|
64
98
|
|
99
|
+
def stop_storages
|
100
|
+
@storages.each(&:stop!)
|
101
|
+
loop do
|
102
|
+
not_exitable = @storages.any? do |storage|
|
103
|
+
if storage.exitable?
|
104
|
+
false
|
105
|
+
else
|
106
|
+
@logger.debug "[status] #{storage.name}: #{storage.status || 'not exitable'.freeze}"
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
break unless not_exitable
|
111
|
+
sleep 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
65
115
|
private
|
66
116
|
|
67
117
|
def on_tweet(account, tweet)
|
@@ -26,19 +26,26 @@ module Akane
|
|
26
26
|
@logger.info "Stream : Starting"
|
27
27
|
|
28
28
|
@thread = Thread.new do
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
begin
|
30
|
+
stream.user do |obj|
|
31
|
+
case obj
|
32
|
+
when Twitter::Tweet
|
33
|
+
invoke(:tweet, obj)
|
34
|
+
when Twitter::DirectMessage
|
35
|
+
invoke(:message, obj)
|
36
|
+
when Twitter::Streaming::DeletedTweet
|
37
|
+
invoke(:delete, obj.user_id, obj.id)
|
38
|
+
when Twitter::Streaming::Event
|
39
|
+
invoke(:event,
|
40
|
+
'event' => obj.name, 'source' => obj.source,
|
41
|
+
'target' => obj.target, 'target_object' => obj.target_object)
|
42
|
+
end
|
41
43
|
end
|
44
|
+
rescue Exception => e
|
45
|
+
raise e if defined?(Twitter::Streaming::MockClient)
|
46
|
+
@logger.error 'Error on stream'
|
47
|
+
@logger.error e.inspect
|
48
|
+
@logger.error e.backtrace
|
42
49
|
end
|
43
50
|
end
|
44
51
|
|
data/lib/akane/recorder.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'timeout'
|
2
3
|
|
3
4
|
module Akane
|
4
5
|
class Recorder
|
5
|
-
|
6
|
+
class Stop < Exception; end # :nodoc:
|
7
|
+
|
8
|
+
def initialize(storages, timeout: 20, logger: Logger.new(nil))
|
6
9
|
@storages = storages
|
7
10
|
@logger = logger
|
8
11
|
@queue = Queue.new
|
9
12
|
@recently_performed = RoundrobinFlags.new(1000)
|
13
|
+
@timeout = timeout
|
14
|
+
@stop = false
|
10
15
|
end
|
11
16
|
|
12
17
|
def queue_length
|
@@ -14,21 +19,25 @@ module Akane
|
|
14
19
|
end
|
15
20
|
|
16
21
|
def record_tweet(account, tweet)
|
22
|
+
return self if @stop
|
17
23
|
@queue << [:record_tweet, account, tweet]
|
18
24
|
self
|
19
25
|
end
|
20
26
|
|
21
27
|
def mark_as_deleted(account, user_id, tweet_id)
|
28
|
+
return self if @stop
|
22
29
|
@queue << [:mark_as_deleted, account, user_id, tweet_id]
|
23
30
|
self
|
24
31
|
end
|
25
32
|
|
26
33
|
def record_message(account, message)
|
34
|
+
return self if @stop
|
27
35
|
@queue << [:record_message, account, message]
|
28
36
|
self
|
29
37
|
end
|
30
38
|
|
31
39
|
def record_event(account, event)
|
40
|
+
return self if @stop
|
32
41
|
@queue << [:record_event, account, event]
|
33
42
|
self
|
34
43
|
end
|
@@ -50,9 +59,18 @@ module Akane
|
|
50
59
|
|
51
60
|
@storages.each do |storage|
|
52
61
|
begin
|
53
|
-
|
62
|
+
timeout(@timeout) do
|
63
|
+
storage.__send__(action, account, *payload)
|
64
|
+
end
|
65
|
+
|
66
|
+
rescue Timeout::Error => e
|
67
|
+
raise e if raise_errors
|
68
|
+
@logger.warn "#{storage.name} (#{action}) timed out"
|
69
|
+
|
70
|
+
rescue Interrupt, SignalException, SystemExit => e
|
71
|
+
raise e
|
72
|
+
|
54
73
|
rescue Exception => e
|
55
|
-
raise e if e === Interrupt
|
56
74
|
raise e if raise_errors
|
57
75
|
@logger.error "Error while recorder performing to #{storage.inspect}: #{e.inspect}"
|
58
76
|
@logger.error e.backtrace
|
@@ -61,16 +79,36 @@ module Akane
|
|
61
79
|
end
|
62
80
|
|
63
81
|
def run(raise_errors = false)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
82
|
+
@running_thread = Thread.new do
|
83
|
+
loop do
|
84
|
+
begin
|
85
|
+
begin
|
86
|
+
self.dequeue(raise_errors)
|
87
|
+
rescue Interrupt, SignalException, Stop
|
88
|
+
end
|
89
|
+
|
90
|
+
if @stop
|
91
|
+
break if self.queue_length.zero?
|
92
|
+
@logger.info "processing queue: #{self.queue_length} remaining."
|
93
|
+
end
|
94
|
+
rescue Exception => e
|
95
|
+
raise e if raise_errors
|
96
|
+
@logger.error "Error while recorder dequing: #{e.inspect}"
|
97
|
+
@logger.error e.backtrace
|
98
|
+
end
|
72
99
|
end
|
100
|
+
|
101
|
+
@logger.info "Recorder stopped."
|
102
|
+
@stop = false
|
73
103
|
end
|
104
|
+
|
105
|
+
@running_thread.join
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def stop!
|
110
|
+
@stop = true
|
111
|
+
@running_thread.raise Stop
|
74
112
|
end
|
75
113
|
|
76
114
|
class RoundrobinFlags
|
@@ -4,6 +4,11 @@ module Akane
|
|
4
4
|
def initialize(config: raise(ArgumentError, 'missing config'), logger: Logger.new($stdout))
|
5
5
|
@config = config
|
6
6
|
@logger = logger
|
7
|
+
@stop = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
self.class.name
|
7
12
|
end
|
8
13
|
|
9
14
|
def record_tweet(account, tweet)
|
@@ -21,6 +26,18 @@ module Akane
|
|
21
26
|
def record_message(account, message)
|
22
27
|
raise NotImplementedError
|
23
28
|
end
|
29
|
+
|
30
|
+
def stop!
|
31
|
+
@stop = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def exitable?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def status
|
39
|
+
nil
|
40
|
+
end
|
24
41
|
end
|
25
42
|
end
|
26
43
|
end
|
data/lib/akane/storages/file.rb
CHANGED
@@ -16,6 +16,10 @@ module Akane
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
def name
|
20
|
+
@name ||= "#{self.class.name}:#{@config["dir"]}"
|
21
|
+
end
|
22
|
+
|
19
23
|
def record_tweet(account, tweet)
|
20
24
|
timeline_io.puts "[#{tweet[:created_at].xmlschema}][#{account}] #{tweet[:user][:screen_name]}: " \
|
21
25
|
"#{tweet[:text].gsub(/\r?\n/,' ')} (#{tweet[:user][:id]},#{tweet[:id]})"
|
data/lib/akane/version.rb
CHANGED
data/spec/manager_spec.rb
CHANGED
@@ -22,7 +22,8 @@ describe Akane::Manager do
|
|
22
22
|
"token" => "consumer-token", "secret" => "consumer-secret"
|
23
23
|
},
|
24
24
|
"accounts" => conf_accounts,
|
25
|
-
"storages" => conf_storages
|
25
|
+
"storages" => conf_storages,
|
26
|
+
"timeout" => 72,
|
26
27
|
).tap { |_| _.stub(logger: Logger.new(nil)) }
|
27
28
|
end
|
28
29
|
|
@@ -49,7 +50,7 @@ describe Akane::Manager do
|
|
49
50
|
it "creates recorder with storages" do
|
50
51
|
storage = double("storage")
|
51
52
|
Akane::Storages::Mock.stub(new: storage)
|
52
|
-
Akane::Recorder.should_receive(:new).with([storage], logger: config.logger).and_call_original
|
53
|
+
Akane::Recorder.should_receive(:new).with([storage], timeout: 72, logger: config.logger).and_call_original
|
53
54
|
|
54
55
|
subject.prepare
|
55
56
|
end
|
data/spec/recorder_spec.rb
CHANGED
@@ -66,21 +66,39 @@ describe Akane::Recorder do
|
|
66
66
|
end
|
67
67
|
|
68
68
|
describe "#run" do
|
69
|
-
before do
|
70
|
-
@th = Thread.new { subject.run(true) }
|
71
|
-
@th.abort_on_exception = true
|
72
|
-
end
|
73
|
-
|
74
69
|
it "continues dequeuing the queue" do
|
75
70
|
storages[0].should_receive(:record_tweet).with('a', {:id => 42})
|
76
71
|
storages[0].should_receive(:record_tweet).with('b', {:id => 43})
|
72
|
+
|
73
|
+
@th = Thread.new { subject.run(true) }
|
74
|
+
@th.abort_on_exception = true
|
75
|
+
|
76
|
+
15.times { break if @th.status == "sleep"; sleep 0.1 }
|
77
|
+
|
77
78
|
subject.record_tweet('a', :id => 42)
|
78
79
|
subject.record_tweet('b', :id => 43)
|
79
|
-
10.times { break if subject.queue_length.zero?; sleep 0.1 }
|
80
|
-
end
|
81
80
|
|
82
|
-
|
81
|
+
15.times { break if subject.queue_length.zero?; sleep 0.1 }
|
83
82
|
@th.kill if @th && @th.alive?
|
84
83
|
end
|
85
84
|
end
|
85
|
+
|
86
|
+
describe "#stop!" do
|
87
|
+
it "stops gracefully" do
|
88
|
+
storages[0].should_receive(:record_tweet).with('a', {:id => 42})
|
89
|
+
allow(storages[0]).to receive(:exitable?).and_return(true)
|
90
|
+
|
91
|
+
@th = Thread.new { subject.run(true) }
|
92
|
+
@th.abort_on_exception = true
|
93
|
+
15.times { break if @th.status == "sleep"; sleep 0.1 }
|
94
|
+
|
95
|
+
subject.record_tweet('a', :id => 42)
|
96
|
+
subject.stop!
|
97
|
+
subject.record_tweet('b', :id => 42)
|
98
|
+
|
99
|
+
15.times { break unless @th.alive?; sleep 0.1 }
|
100
|
+
|
101
|
+
expect(@th).not_to be_alive
|
102
|
+
end
|
103
|
+
end
|
86
104
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: akane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shota Fukumori (sora_h)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elasticsearch
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.4.7
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sigdump
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: bundler
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +136,7 @@ files:
|
|
122
136
|
- LICENSE.txt
|
123
137
|
- README.md
|
124
138
|
- Rakefile
|
139
|
+
- akane.example.yml
|
125
140
|
- akane.gemspec
|
126
141
|
- bin/akane
|
127
142
|
- lib/akane.rb
|
@@ -169,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
184
|
version: '0'
|
170
185
|
requirements: []
|
171
186
|
rubyforge_project:
|
172
|
-
rubygems_version: 2.2.
|
187
|
+
rubygems_version: 2.2.2
|
173
188
|
signing_key:
|
174
189
|
specification_version: 4
|
175
190
|
summary: Log your timeline to something
|
@@ -185,4 +200,3 @@ test_files:
|
|
185
200
|
- spec/support/mock_tweetstream.rb
|
186
201
|
- spec/support/mock_twitter_streaming.rb
|
187
202
|
- spec/util_spec.rb
|
188
|
-
has_rdoc:
|