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.
@@ -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