akane 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|