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.
@@ -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
@@ -8,7 +8,7 @@ require 'em-http'
8
8
  module BirdGrinder
9
9
  include Perennial
10
10
 
11
- VERSION = [0, 1, 2, 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(" ", 2)
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
- INITIAL_DELAYS = {:http => 10, :network => 0.25}
10
- MAX_DELAYS = {:http => 240, :network => 16}
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) || :name
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 = create_request.send(type, http_options(type))
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
- logger.debug "Streaming failed with #{type}"
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
- def create_request
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
@@ -1,3 +1,3 @@
1
1
  require 'rubygems'
2
2
  require 'bird_grinder'
3
- BirdGrinder::Settings.root = Pathname.new(__FILE__).dirname.join("..").expand_path
3
+ BirdGrinder::Settings.root = Pathname(__FILE__).dirname.join("..").expand_path
@@ -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.new(__FILE__).dirname.join("..").expand_path
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
@@ -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.new(__FILE__).dirname.join("..").expand_path
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.2.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-03 00:00:00 +08:00
12
+ date: 2009-11-08 00:00:00 +08:00
13
13
  default_executable: birdgrinder
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency