birdgrinder 0.1.2.1 → 0.1.3.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.
- 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
|