birdgrinder 0.1.2.1 → 0.1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/birdgrinder +0 -4
- data/lib/bird_grinder.rb +4 -3
- data/lib/bird_grinder/command_handler.rb +1 -1
- data/lib/bird_grinder/tweeter/streaming.rb +3 -0
- data/lib/bird_grinder/tweeter/streaming_request.rb +42 -10
- data/templates/boot.erb +1 -1
- data/templates/test_helper.erb +1 -1
- data/test/test_helper.rb +1 -1
- metadata +2 -2
data/bin/birdgrinder
CHANGED
@@ -13,14 +13,11 @@ BirdGrinder::Application.processing(ARGV) do |a|
|
|
13
13
|
|
14
14
|
a.option(:force, "force the creation of the application")
|
15
15
|
a.add("create PATH", "Creates a BirdGrinder instance at a specified location") do |path, options|
|
16
|
-
|
17
16
|
path = File.expand_path(path)
|
18
17
|
if File.exists?(path) && !options[:force]
|
19
18
|
die! "The path you tried to use, #{path}, already exists. Please try another or use the --force option"
|
20
19
|
end
|
21
|
-
|
22
20
|
setup_generator path
|
23
|
-
|
24
21
|
folders 'tmp', 'config', 'handlers', 'test'
|
25
22
|
template 'boot.erb', 'config/boot.rb'
|
26
23
|
template 'setup.erb', 'config/setup.rb'
|
@@ -28,7 +25,6 @@ BirdGrinder::Application.processing(ARGV) do |a|
|
|
28
25
|
template 'debug_handler.erb', 'handlers/debug_handler.rb'
|
29
26
|
template 'hello_world_handler.erb', 'handlers/hello_world_handler.rb'
|
30
27
|
template 'rakefile.erb', 'Rakefile'
|
31
|
-
|
32
28
|
end
|
33
29
|
|
34
30
|
end
|
data/lib/bird_grinder.rb
CHANGED
@@ -8,7 +8,7 @@ require 'em-http'
|
|
8
8
|
module BirdGrinder
|
9
9
|
include Perennial
|
10
10
|
|
11
|
-
VERSION = [0, 1,
|
11
|
+
VERSION = [0, 1, 3, 0]
|
12
12
|
|
13
13
|
def self.version(include_minor = false)
|
14
14
|
VERSION[0, (include_minor ? 4 : 3)].join(".")
|
@@ -21,9 +21,10 @@ module BirdGrinder
|
|
21
21
|
l.register_controller :console, 'BirdGrinder::Console'
|
22
22
|
end
|
23
23
|
|
24
|
-
has_library :cacheable, :tweeter, :client, :base, :command_handler,
|
25
|
-
:console, :queue_processor, :stream_handler
|
24
|
+
has_library :cacheable, :tweeter, :client, :base, :command_handler, :console, :stream_handler
|
26
25
|
|
27
26
|
extends_library :loader
|
28
27
|
|
28
|
+
autoload :QueueProcessor, 'bird_grinder/queue_processor'
|
29
|
+
|
29
30
|
end
|
@@ -59,7 +59,7 @@ module BirdGrinder
|
|
59
59
|
end
|
60
60
|
else
|
61
61
|
logger.debug "Checking for command in direct message"
|
62
|
-
command, data = options.text.split(
|
62
|
+
command, data = options.text.split(/\s+/, 2)
|
63
63
|
end
|
64
64
|
if (command_name = extract_command_name(command)).present?
|
65
65
|
logger.info "Processing command '#{command_name}' for #{user}"
|
@@ -15,6 +15,9 @@ module BirdGrinder
|
|
15
15
|
|
16
16
|
attr_accessor :parent
|
17
17
|
|
18
|
+
# Initializes a streaming subclient for a given tweeter
|
19
|
+
#
|
20
|
+
# @param [BirdGrinder::Tweeter] parent the parent tweeter which we use to dispatch events.
|
18
21
|
def initialize(parent)
|
19
22
|
@parent = parent
|
20
23
|
logger.debug "Initializing Streaming Support"
|
@@ -3,21 +3,36 @@ require 'cgi'
|
|
3
3
|
|
4
4
|
module BirdGrinder
|
5
5
|
class Tweeter
|
6
|
+
# A request implementation for the twitter streaming api that correctly:
|
7
|
+
# 1) keeps connections alives
|
8
|
+
# 2) reacts accordingly to errors
|
9
|
+
# 3) handles tweets in an evented way
|
10
|
+
#
|
11
|
+
# It's built around em-http-request internally but also makes use of BirdGrinder::Tweeter
|
12
|
+
# and BirdGrinder::Tweeter::Streaming to provide a nice, user friendly interface.
|
6
13
|
class StreamingRequest
|
7
14
|
is :loggable
|
8
15
|
|
9
|
-
|
10
|
-
|
16
|
+
# Values / rates as suggested in the twitter api.
|
17
|
+
INITIAL_DELAYS = {:http => 10, :network => 0.25}
|
18
|
+
MAX_DELAYS = {:http => 240, :network => 16}
|
11
19
|
DELAY_CALCULATOR = {
|
12
20
|
:http => L { |v| v * 2 },
|
13
21
|
:network => L { |v| v + INITIAL_DELAYS[:network] }
|
14
22
|
}
|
15
23
|
|
24
|
+
# Creates a streaming request.
|
25
|
+
#
|
26
|
+
# @param [BirdGrinder::Tweeter] parent the tweeter parent class
|
27
|
+
# @param [Sybol, String] name the name of the stream type
|
28
|
+
# @param [Hash] options The options for this request
|
29
|
+
# @option options [Symbol, String] :path the path component used for the streaming api e.g. sample for filter.
|
30
|
+
# @options options [Object] :metadata generic data to be attached to received tweets
|
16
31
|
def initialize(parent, name, options = {})
|
17
32
|
logger.debug "Creating stream '#{name}' with options: #{options.inspect}"
|
18
33
|
@parent = parent
|
19
34
|
@name = name
|
20
|
-
@path = options.delete(:path) ||
|
35
|
+
@path = options.delete(:path) || name
|
21
36
|
@metadata = options.delete(:metadata) || {}
|
22
37
|
@options = options
|
23
38
|
@failure_delay = nil
|
@@ -25,11 +40,12 @@ module BirdGrinder
|
|
25
40
|
@failure_reason = nil
|
26
41
|
end
|
27
42
|
|
43
|
+
# Starts the streaming connection
|
28
44
|
def perform
|
29
45
|
logger.debug "Preparing to start stream"
|
30
46
|
@stream_processor = nil
|
31
47
|
type = request_method
|
32
|
-
http =
|
48
|
+
http = EventMachine::HttpRequest.new(full_url).send(type, http_options(type))
|
33
49
|
# Handle failures correctly so we can back off
|
34
50
|
@current_request = http
|
35
51
|
http.errback { fail!(:network)}
|
@@ -37,10 +53,14 @@ module BirdGrinder
|
|
37
53
|
http.stream { |c| receive_chunk(c) }
|
38
54
|
end
|
39
55
|
|
56
|
+
# Process a failure and responds accordingly.
|
57
|
+
#
|
58
|
+
# @param [Symbol] type the type of error, one of :http or :network
|
40
59
|
def fail!(type)
|
41
|
-
|
60
|
+
suffix = type == :http ? " (Error Code #{@current_request.response_header.status})" : ""
|
61
|
+
logger.debug "Streaming failed with #{type}#{suffix}"
|
42
62
|
if @failure_count == 0 || @failure_reason != type
|
43
|
-
logger.debug "Instantly restarting (#{@failure_count == 0 ? "First failure" : "Different type"})"
|
63
|
+
logger.debug "Instantly restarting (#{@failure_count == 0 ? "First failure" : "Different type of failure"})"
|
44
64
|
EM.next_tick { perform }
|
45
65
|
else
|
46
66
|
@failure_delay ||= INITIAL_DELAYS[type]
|
@@ -56,14 +76,16 @@ module BirdGrinder
|
|
56
76
|
logger.debug "Failed #{@failure_count} times with #{@failure_reason}"
|
57
77
|
end
|
58
78
|
|
59
|
-
|
60
|
-
EventMachine::HttpRequest.new(full_url)
|
61
|
-
end
|
62
|
-
|
79
|
+
# Returns the current stream processor, creating a new one if it hasn't been initialized yet.
|
63
80
|
def stream_processor
|
64
81
|
@stream_processor ||= StreamProcessor.new(@parent, @name, @metadata)
|
65
82
|
end
|
66
83
|
|
84
|
+
# Processes a chunk of the incoming request, parsing it with the stream
|
85
|
+
# processor as well as resetting anything that is used to track failure
|
86
|
+
# (as a chunk implies that it's successful)
|
87
|
+
#
|
88
|
+
# @param [String] c the chunk of data to receive
|
67
89
|
def receive_chunk(c)
|
68
90
|
return unless @current_request.response_header.status == 200
|
69
91
|
if !@failure_reason.nil?
|
@@ -74,10 +96,18 @@ module BirdGrinder
|
|
74
96
|
stream_processor.receive_chunk(c)
|
75
97
|
end
|
76
98
|
|
99
|
+
# Returns a set of options that apply to the request no matter
|
100
|
+
# what method is used to send the request. It's important that
|
101
|
+
# this is used for credentials as well as making sure there is
|
102
|
+
# no timeout on the connection
|
77
103
|
def default_request_options
|
78
104
|
{:head => {'Authorization' => @parent.auth_credentials}, :timeout => 0}
|
79
105
|
end
|
80
106
|
|
107
|
+
# Returns normalized http options for the current request, built
|
108
|
+
# on top of default_request_options and a few other details.
|
109
|
+
#
|
110
|
+
# @param [Symbol] type the type of request - :post or :get
|
81
111
|
def http_options(type)
|
82
112
|
base = self.default_request_options
|
83
113
|
if @options.present?
|
@@ -92,6 +122,7 @@ module BirdGrinder
|
|
92
122
|
base
|
93
123
|
end
|
94
124
|
|
125
|
+
# Returns the correct http method to be used for the current path.
|
95
126
|
def request_method
|
96
127
|
{:filter => :post,
|
97
128
|
:sample => :get,
|
@@ -100,6 +131,7 @@ module BirdGrinder
|
|
100
131
|
}.fetch(@path, :get)
|
101
132
|
end
|
102
133
|
|
134
|
+
# Returns the full streaming api associated with this url.
|
103
135
|
def full_url
|
104
136
|
@full_url ||= (Streaming.streaming_base_url / Streaming.api_version.to_s / "statuses" / "#{@path}.json")
|
105
137
|
end
|
data/templates/boot.erb
CHANGED
data/templates/test_helper.erb
CHANGED
@@ -7,7 +7,7 @@ require 'shoulda'
|
|
7
7
|
require 'redgreen' if RUBY_VERSION < "1.9"
|
8
8
|
|
9
9
|
require 'pathname'
|
10
|
-
root_directory = Pathname
|
10
|
+
root_directory = Pathname(__FILE__).dirname.join("..").expand_path
|
11
11
|
require root_directory.join("config", "boot")
|
12
12
|
|
13
13
|
class Test::Unit::TestCase
|
data/test/test_helper.rb
CHANGED
@@ -7,7 +7,7 @@ require 'shoulda'
|
|
7
7
|
require 'redgreen' if RUBY_VERSION < "1.9"
|
8
8
|
|
9
9
|
require 'pathname'
|
10
|
-
root_directory = Pathname
|
10
|
+
root_directory = Pathname(__FILE__).dirname.join("..").expand_path
|
11
11
|
require root_directory.join("lib", "bird_grinder")
|
12
12
|
|
13
13
|
class Test::Unit::TestCase
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: birdgrinder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darcy Laycock
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-08 00:00:00 +08:00
|
13
13
|
default_executable: birdgrinder
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|