gnip_api 1.1.3 → 1.2.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +16 -37
  4. data/lib/gnip_api/adapter.rb +42 -28
  5. data/lib/gnip_api/configuration.rb +4 -6
  6. data/lib/gnip_api/errors.rb +3 -0
  7. data/lib/gnip_api/gnip/gnip_data.rb +1 -1
  8. data/lib/gnip_api/gnip/system_message.rb +4 -0
  9. data/lib/gnip_api/power_track/buffer.rb +42 -0
  10. data/lib/gnip_api/power_track/rule.rb +77 -0
  11. data/lib/gnip_api/power_track/rule_validator.rb +6 -0
  12. data/lib/gnip_api/power_track/rules.rb +81 -0
  13. data/lib/gnip_api/power_track/stream.rb +122 -0
  14. data/lib/gnip_api/request.rb +8 -1
  15. data/lib/gnip_api/response.rb +21 -0
  16. data/lib/gnip_api/search.rb +108 -0
  17. data/lib/gnip_api/version.rb +1 -1
  18. data/lib/gnip_api.rb +5 -7
  19. data/spec/fixtures/rule_value_examples.json +27 -0
  20. data/spec/gnip_api/adapter_spec.rb +0 -87
  21. data/spec/gnip_api/configuration_spec.rb +0 -15
  22. data/spec/gnip_api/{apis/power_track → power_track}/buffer_spec.rb +2 -2
  23. data/spec/gnip_api/power_track/rule_spec.rb +89 -0
  24. data/spec/gnip_api/{apis/power_track → power_track}/rules_spec.rb +8 -8
  25. data/spec/gnip_api/power_track/stream_spec.rb +53 -0
  26. data/spec/gnip_api/response_spec.rb +27 -0
  27. data/spec/gnip_api/{apis/search_spec.rb → search_spec.rb} +9 -9
  28. data/spec/spec_helper.rb +4 -3
  29. metadata +20 -26
  30. data/lib/gnip_api/adapters/base_adapter.rb +0 -94
  31. data/lib/gnip_api/adapters/httparty_adapter.rb +0 -65
  32. data/lib/gnip_api/apis/power_track/buffer.rb +0 -38
  33. data/lib/gnip_api/apis/power_track/rule.rb +0 -33
  34. data/lib/gnip_api/apis/power_track/rule_validator.rb +0 -8
  35. data/lib/gnip_api/apis/power_track/rules.rb +0 -83
  36. data/lib/gnip_api/apis/power_track/stream.rb +0 -84
  37. data/lib/gnip_api/apis/search.rb +0 -109
  38. data/spec/gnip_api/adapters/base_adapter_spec.rb +0 -0
  39. data/spec/gnip_api/adapters/httparty_adapter_spec.rb +0 -0
  40. data/spec/gnip_api/apis/power_track/rule_spec.rb +0 -62
  41. data/spec/gnip_api/apis/power_track/stream_spec.rb +0 -93
  42. data/spec/lib/test_adapter.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a4b23aa44097600cdcd2a60ed769511a39999a6a
4
- data.tar.gz: b94409aca9eb2a64c653ba006a45db6c3fb3d806
3
+ metadata.gz: 515a1f5c48c72c4f23dfc7e4882d6f7b8b47067c
4
+ data.tar.gz: f4775d00111fb0811aefb798aa178357927ccf02
5
5
  SHA512:
6
- metadata.gz: e66a5a77b304723a7a8416f6d4674426c19ae70526ea3eeeb94473ec8beaa09b5942918b02b92c933cbc948b50db10b57c6080df08b527168c88c7a500fb0dbd
7
- data.tar.gz: 8c886b7b16b51c2461885a33e11f1813b5fc847acccc43e0d6cad47d1956bfb490a4a4d5ab9c964d8f026b26bac0d4dc27673c661485b1547889abf65fcbae2d
6
+ metadata.gz: 358bd8b2aba8653f782a747793b1f4580712ef74c3b188ef9a5fd65dfb8cd5cfeab1d8238327a147b42a9226abc925bab2999bb83c74f929abb917fe0a9c0ebd
7
+ data.tar.gz: 89bd54df5d5e31a04f467996e104651c4ace873de6748a96d33788eaf89e01d022aa26f3c81f8261998a138346f9ebcdc192ca2c5f5426267cbc7502bad0df10
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gnip_api (1.1.3)
4
+ gnip_api (1.2.0)
5
5
  addressable
6
6
  httparty
7
7
  yajl-ruby
data/README.md CHANGED
@@ -6,11 +6,9 @@ Connect with different Gnip APIs and get data from streams.
6
6
 
7
7
  ## Recent Changes
8
8
 
9
- - Added a debug mode to output more information on logger from adapter
10
- - 2.0 APIs implemented partially, more will come soon
11
- - Fixed a memory issue with HTTParty
12
- - Timeout for requests added to fail if API is non responsive, time can be configured
13
- - Search API returns parsed data either for counts or activities, which also makes Search API usable to get activities now
9
+ - Removed adapter customization and opted to use HTTParty as main adapter
10
+ - Removed output option for stream
11
+ - Added different methods for consuming stream
14
12
 
15
13
  ## Notes
16
14
 
@@ -43,7 +41,6 @@ GnipApi.configure |config|
43
41
  config.user = 'someone' # Gnip Account Username
44
42
  config.password = 'something' # Gnip Password
45
43
  config.account = 'myGnipAccount' # Your accounts name
46
- config.adapter_class = SomeAdapter # You can define your own adapter, more in the following section
47
44
  config.logger = Logger.new('myLog.log') # You can also provide a custom logger
48
45
  config.source = 'twitter' # General source, if none defined when quering, this will be used
49
46
  config.label = 'mystream' # General stream label, if none defined when quering, this will be used
@@ -83,19 +80,19 @@ The Search API allows you to get counts or activities in a time period, with a m
83
80
  To access the Search API you will need a rule first, you can use PowerTrack Rule object for it:
84
81
 
85
82
  ```ruby
86
- rule = GnipApi::Apis::PowerTrack::Rule.new :value => 'keyword1 OR keyword2'
83
+ rule = GnipApi::PowerTrack::Rule.new :value => 'keyword1 OR keyword2'
87
84
  ```
88
85
 
89
86
  Then you can query the search endpoint to get counts or activities. For counts:
90
87
 
91
88
  ```ruby
92
- results = GnipApi::Apis::Search.new.counts :rule => rule
89
+ results = GnipApi::Search.new.counts :rule => rule
93
90
  ```
94
91
 
95
92
  For activities:
96
93
 
97
94
  ```ruby
98
- results = GnipApi::Apis::Search.new.activities :rule => rule
95
+ results = GnipApi::Search.new.activities :rule => rule
99
96
  ```
100
97
 
101
98
  Responses are parsed, so you can then use the output normally as any other Ruby object. For the case of activities, they get converted to Gnip::Activity objects, and have all the rest parsed as they would came from stream.
@@ -103,19 +100,19 @@ Responses are parsed, so you can then use the output normally as any other Ruby
103
100
  You can set different parameters:
104
101
 
105
102
  ```ruby
106
- results = GnipApi::Apis::Search.new.counts :rule => rule, :from_date => DateTime.parse('2016-01-01 00:00'), :to_date => DateTime.parse('2016-05-01 22:00'), :bucket => 'day'
103
+ results = GnipApi::Search.new.counts :rule => rule, :from_date => DateTime.parse('2016-01-01 00:00'), :to_date => DateTime.parse('2016-05-01 22:00'), :bucket => 'day'
107
104
  ```
108
105
 
109
106
  For activities, there are a few extra considerations:
110
107
 
111
108
  - A param ```:max_results``` indicates how many activities to return on a response, valid values are from 10 to 500, default is 100, this param does not work on counts.
112
- - As you noticed, you pass a ```GnipApi::Apis::PowerTrack::Rule``` object to the search endpoint, and as you may also know, these objects have mostly 2 things: value (actual rule), and tag. When querying activities on the Search API, you can optionally use a tag that is returned on the activity, along with the rule. This tag is deduced from the rule object you pass, in other words, if you want a tag, add it on the ```GnipApi::Apis::PowerTrack::Rule``` object, it's not a valid param for the method.
109
+ - As you noticed, you pass a ```GnipApi::PowerTrack::Rule``` object to the search endpoint, and as you may also know, these objects have mostly 2 things: value (actual rule), and tag. When querying activities on the Search API, you can optionally use a tag that is returned on the activity, along with the rule. This tag is deduced from the rule object you pass, in other words, if you want a tag, add it on the ```GnipApi::PowerTrack::Rule``` object, it's not a valid param for the method.
113
110
  - The ```:bucket``` option is only for counts.
114
111
 
115
112
  When you query for more than 30 days or more activities than ```:max_results```, the results will include a ```:next``` token to iterate over the remaining pages. You can instantly feed this token to a following request with same parameters:
116
113
 
117
114
  ```ruby
118
- results = GnipApi::Apis::Search.new.counts :rule => rule, :from_date => DateTime.parse('2016-01-01 00:00'), :to_date => DateTime.parse('2016-05-01 22:00'), :bucket => 'day', :next_token => 'token_from_previous_request'
115
+ results = GnipApi::Search.new.counts :rule => rule, :from_date => DateTime.parse('2016-01-01 00:00'), :to_date => DateTime.parse('2016-05-01 22:00'), :bucket => 'day', :next_token => 'token_from_previous_request'
119
116
  ```
120
117
 
121
118
  ### PowerTrack
@@ -124,14 +121,14 @@ PowerTrack API has various functions. You can upload, delete and get rules and y
124
121
 
125
122
  ```ruby
126
123
  rules = []
127
- rules << GnipApi::Apis::PowerTrack::Rule.new :value => 'keyword1 OR keyword2', :tag => 'first_rule'
128
- rules << GnipApi::Apis::PowerTrack::Rule.new :value => 'keyword3 keyword4', :tag => 'second_rule'
124
+ rules << GnipApi::PowerTrack::Rule.new :value => 'keyword1 OR keyword2', :tag => 'first_rule'
125
+ rules << GnipApi::PowerTrack::Rule.new :value => 'keyword3 keyword4', :tag => 'second_rule'
129
126
  ```
130
127
 
131
128
  Once you have your rule objects set, you can put them into an array and feed them to the PowerTrack Rules API:
132
129
 
133
130
  ```ruby
134
- GnipApi::Apis::PowerTrack::Rules.new.create rules
131
+ GnipApi::PowerTrack::Rules.new.create rules
135
132
  ```
136
133
 
137
134
  That will upload the rules to the stream. The endpoint doesn't return anything on success but it will validate rules before applying and any syntax error will be raised as an error.
@@ -139,13 +136,13 @@ That will upload the rules to the stream. The endpoint doesn't return anything o
139
136
  To get a list of rules defined in the stream:
140
137
 
141
138
  ```ruby
142
- GnipApi::Apis::PowerTrack::Rules.new.list
139
+ GnipApi::PowerTrack::Rules.new.list
143
140
  ```
144
141
 
145
- That will return an array of GnipRule::Apis::PowerTrack::Rule objects. In the same way as the upload the delete method removes 1 or more rules:
142
+ That will return an array of GnipRule::PowerTrack::Rule objects. In the same way as the upload the delete method removes 1 or more rules:
146
143
 
147
144
  ```ruby
148
- GnipApi::Apis::PowerTrack::Rules.new.delete rules
145
+ GnipApi::PowerTrack::Rules.new.delete rules
149
146
  ```
150
147
 
151
148
  Same as upload, no response from Gnip when deleting.
@@ -154,7 +151,7 @@ Same as upload, no response from Gnip when deleting.
154
151
  Finally, you can stream the activities and do something with them:
155
152
 
156
153
  ```ruby
157
- GnipApi::Apis::PowerTrack::Stream.new.consume do |messages|
154
+ GnipApi::PowerTrack::Stream.new.consume do |messages|
158
155
  messages.select{|m| m.activity?}.each{|a| puts a.body}
159
156
  messages.select{|m| m.system_message?}.each{|s| puts s.message}
160
157
  end
@@ -167,24 +164,6 @@ There are a few considerations to make when doing this:
167
164
  - Be careful when putting this into a daemon, closing the stream can be tricky given how this was done
168
165
  - I've experience issues with a Zlib error that I currently couldn't debug and fix, if you build a daemon for this, be sure to code restart procedures
169
166
 
170
- ### Adapters
171
-
172
- GnipApi is not dependent of a single adapter (there's a dependency with HTTParty, but shhh... it won't last too long). You can use one of the provided adapters, or you can make your own, using the BaseAdapter class. You only need to implement the desired connector POST, GET and DELETE methods. There's an extra stream_get method, but it's just a variant of GET. Keep in mind that Gnip uses compression, I found out that Excon doesn't decompress responses by default, just to name an example.
173
- The custom adapter does not require to live within the gem files, as long as GnipApi has access to your class, just put it in the config and you're ready to go. See the current adapters for reference.
174
-
175
- ## Debug mode
176
-
177
- At any time you can enable/disable the debug mode like this:
178
-
179
- ```ruby
180
- GnipApi.configuration.debug = true
181
- ```
182
-
183
- This will output information about the request/response. Response body is not returned to avoid excessive logging.
184
-
185
- Note that debug data is dependent on what adapter you are using. You should be able to dump the necesary information from
186
- your adapter and call the internal debugger to log the information.
187
-
188
167
  ## WIP State
189
168
 
190
169
  GnipApi is a WIP. Call it a beta, alpha, gem that has part of the features, whatever. It is currently usable and it's being used by... well.. me. The custom adapter feature is there, and some of the APIs of Gnip were implemented. I'll be coding more things into this, such as other APIs, request retries, error handling, different adapters for known connectors like RestClient or HTTP/net, etc.
@@ -4,62 +4,76 @@ module GnipApi
4
4
  POST = 'POST'
5
5
  DELETE = 'DELETE'
6
6
 
7
- attr_reader :adapter
7
+ attr_reader :logger, :debug
8
8
 
9
9
  def initialize
10
10
  raise GnipApi::Errors::MissingCredentials unless GnipApi.credentials?
11
- raise GnipApi::Errors::MissingAdapter unless GnipApi.adapter_class?
12
- @adapter = GnipApi.config.adapter_class.new
13
11
  @logger = GnipApi.config.logger
14
12
  @debug = GnipApi.config.debug
15
13
  end
16
14
 
17
15
  def get request
18
- log_request(request)
19
- response = adapter.get(request)
20
- check_response_for_errors(response)
16
+ request.log!
17
+ data = HTTParty.get request.uri, :basic_auth => auth, :timeout => default_timeout
18
+ response = create_response(request, data.code, data.body, data.headers)
19
+ response.check_for_errors!
21
20
  return response.body unless response.body.empty?
22
21
  return true
23
22
  end
24
23
 
25
24
  def post request
26
- log_request(request)
27
- response = adapter.post(request)
28
- check_response_for_errors(response)
25
+ request.log!
26
+ data = HTTParty.post request.uri, :basic_auth => auth, :body => request.payload, :timeout => default_timeout
27
+ response = create_response(request, data.code, data.body, data.headers)
28
+ response.check_for_errors!
29
29
  return response.body unless response.body.empty?
30
30
  return true
31
31
  end
32
32
 
33
33
  def delete request
34
- log_request(request)
35
- response = adapter.delete(request)
36
- check_response_for_errors(response)
34
+ request.log!
35
+ data = HTTParty.post request.uri, :basic_auth => auth, :body => request.payload, :timeout => default_timeout
36
+ response = create_response(request, data.code, data.body, data.headers)
37
+ response.check_for_errors!
37
38
  return response.body unless response.body.empty?
38
39
  return true
39
40
  end
40
41
 
41
42
  def stream_get request
42
- log_request(request)
43
- adapter.stream_get(request) do |data|
44
- yield(data)
43
+ request.log!
44
+ begin
45
+ HTTParty.get request.uri, :headers => request.headers, :stream_body => true, :basic_auth => auth do |data|
46
+ yield(data)
47
+ end
48
+ rescue Zlib::BufError => error
49
+ GnipApi.config.logger.error "STREAM ERROR -> #{error.class} -- #{error.message}\n" + error.backtrace.join("\n")
50
+ raise error
45
51
  end
46
52
  end
47
53
 
48
- def check_response_for_errors response
49
- if response.ok?
50
- @logger.info "#{response.request_method} request to #{response.request_uri} returned with status #{response.status} OK"
51
- else
52
- error_message = response.error_message
53
- @logger.error "#{response.request_method} request to #{response.request_uri} returned with status #{response.status} FAIL: #{error_message}"
54
- raise GnipApi::Errors::Adapter::GnipSoftwareError.new error_message if response.status == 503 || response.status == '503'
55
- raise GnipApi::Errors::Adapter::RateLimitError.new error_message if response.status == 429 || response.status == '429'
56
- raise GnipApi::Errors::Adapter::RequestError.new error_message
57
- end
54
+ private
55
+ def auth
56
+ {
57
+ :username => username,
58
+ :password => password
59
+ }
58
60
  end
59
61
 
60
- private
61
- def log_request request
62
- @logger.info "Starting #{request.request_method} request to #{request.uri}"
62
+ def username
63
+ GnipApi.configuration.user
64
+ end
65
+
66
+ def password
67
+ GnipApi.configuration.password
63
68
  end
69
+
70
+ def default_timeout
71
+ GnipApi.configuration.request_timeout
72
+ end
73
+
74
+ def create_response request, status, body, headers
75
+ GnipApi::Response.new request, status, body, headers
76
+ end
77
+
64
78
  end
65
79
  end
@@ -1,17 +1,15 @@
1
1
  module GnipApi
2
2
  class Configuration
3
- OUTPUT_FORMATS = [:activity, :json, :parsed_json]
4
-
5
- attr_accessor :user, :password, :adapter_class, :account, :logger, :source, :label, :request_timeout, :debug,
6
- :stream_output_format, :enable_gzip
3
+ attr_accessor :user, :password, :account, :logger, :source, :label, :request_timeout, :debug,
4
+ :enable_gzip, :log_level, :buffer_limit
7
5
 
8
6
  def initialize
9
- @adapter_class = GnipApi::Adapters::HTTPartyAdapter
10
7
  @logger = Logger.new('tmp/gnip_api.log')
11
8
  @request_timeout = 60
12
9
  @debug = false
13
- @stream_output_format = :activity
14
10
  @enable_gzip = true
11
+ @log_level = Logger::INFO
12
+ @logger.level = @log_level
15
13
  end
16
14
  end
17
15
  end
@@ -34,6 +34,9 @@ module GnipApi
34
34
  end
35
35
 
36
36
  module PowerTrack
37
+ class StreamDown < StandardError; end
38
+ class BufferTooBig < StandardError; end
39
+
37
40
  class MissingRules < StandardError
38
41
  def initialize
39
42
  @message = 'No rules provided to operate'
@@ -3,7 +3,7 @@ module Gnip
3
3
  attr_reader :matching_rules, :urls, :language
4
4
 
5
5
  def initialize params={}
6
- @matching_rules = params['matching_rules'].map{|r| GnipApi::Apis::PowerTrack::Rule.new(r)} if params['matching_rules']
6
+ @matching_rules = params['matching_rules'].map{|r| GnipApi::PowerTrack::Rule.new(r)} if params['matching_rules']
7
7
  @urls = (params['urls'] ? params['urls'].map{|u| Gnip::Url.new(u)} : [])
8
8
  @language = params['language']
9
9
  end
@@ -27,5 +27,9 @@ module Gnip
27
27
  def to_json
28
28
  @raw.to_json
29
29
  end
30
+
31
+ def log!
32
+ GnipApi.logger.warn "System Message Received: #{message_type} -- #{message} at #{sent}"
33
+ end
30
34
  end
31
35
  end
@@ -0,0 +1,42 @@
1
+ module GnipApi
2
+ module PowerTrack
3
+ class Buffer
4
+ attr_reader :terminator, :data
5
+
6
+ def initialize options={}
7
+ @terminator = options.delete(:terminator) || "\r\n"
8
+ @data = ""
9
+ @limit = GnipApi.config.buffer_limit
10
+ end
11
+
12
+ def size
13
+ @data.size
14
+ end
15
+
16
+ def insert! chunk
17
+ @data << chunk
18
+ end
19
+
20
+ def read!
21
+ objects = @data.split(terminator)[0..-2]
22
+ unless objects.empty?
23
+ # Get the number of chars to read from buffer, counting
24
+ # the size of each splited chunk plus the number of chunks by
25
+ # terminator size, since it's still present in the buffer
26
+ size = objects.map(&:size).reduce(:+) + objects.size * terminator.size
27
+ consume!(size)
28
+ end
29
+ return objects
30
+ end
31
+
32
+ def consume! chars
33
+ @data[0..chars-1] = ''
34
+ end
35
+
36
+ def over_limit?
37
+ return false unless @limit
38
+ return size > @limit
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,77 @@
1
+ module GnipApi
2
+ module PowerTrack
3
+ # Represents a PowerTrack rule with its necesary attribures.
4
+ class Rule
5
+ attr_accessor :value, :tag, :id
6
+
7
+ def initialize params={}
8
+ @value = params[:value] || params['value']
9
+ @tag = params[:tag] || params['tag']
10
+ @id = params[:id] || params['id']
11
+ end
12
+
13
+ def to_json
14
+ attributes.to_json
15
+ end
16
+
17
+ def attributes
18
+ attrs = {}
19
+ attrs[:value] = @value if @value
20
+ attrs[:tag] = @tag if @tag
21
+ attrs[:id] = @id if @id
22
+ attrs
23
+ end
24
+
25
+ def uid
26
+ rule = @value
27
+ rule += "tag:#{@tag}" if @tag
28
+ Digest::SHA2.hexdigest(rule)
29
+ end
30
+
31
+ # Simply checks balance of quotes and parenthesis within the value. Quoted text
32
+ # is sort of escaped as long as it's consistently quoted.
33
+ def consistent? raise_error=false
34
+ characters = value.chars
35
+ parenthesis = 0
36
+ in_double_quotes = false
37
+ in_single_quotes = false
38
+ char = characters.shift
39
+ while char
40
+ parenthesis += 1 if (char == '(')
41
+ parenthesis -= 1 if (char == ')')
42
+
43
+ if char == '"'
44
+ in_double_quotes = true
45
+ char = characters.shift
46
+ while char != '"'
47
+ char = characters.shift
48
+ break if char.nil?
49
+ end
50
+ in_double_quotes = false if char == '"'
51
+ end
52
+
53
+ if char == "'"
54
+ in_single_quotes = true
55
+ char = characters.shift
56
+ while char != "'"
57
+ char = characters.shift
58
+ break if char.nil?
59
+ end
60
+ in_single_quotes = false if char == "'"
61
+ end
62
+
63
+ char = characters.shift
64
+ end
65
+
66
+ if raise_error
67
+ raise ArgumentError.new("Imbalanced parenthesis") if parenthesis != 0
68
+ raise ArgumentError.new("Imbalanced single quotes") if in_single_quotes != false
69
+ raise ArgumentError.new("Imbalanced double quotes") if in_double_quotes != false
70
+ return nil
71
+ end
72
+ return parenthesis == 0 && in_single_quotes == false && in_double_quotes == false
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,6 @@
1
+ module GnipApi
2
+ module PowerTrack
3
+ class RuleValidator
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,81 @@
1
+ # Gnip PowerTrack Rules API
2
+ #
3
+ # Create, delete and list rules of a powetrack stream.
4
+
5
+ module GnipApi
6
+ module PowerTrack
7
+ class Rules
8
+ attr_reader :adapter
9
+
10
+ # In order to do any operation, you need to specify:
11
+ # - label: the label of your stream
12
+ # - source: which data source to use (I think only twitter is available)
13
+ def initialize params={}
14
+ @adapter = GnipApi::Adapter.new
15
+ @label = params[:label] || GnipApi.config.label
16
+ end
17
+
18
+ # Returns an array of defined rules
19
+ def list
20
+ request = create_get_request
21
+ rules = adapter.get(request)
22
+ parse_rules(rules)
23
+ end
24
+
25
+ # Creates the specified rule. Parameters:
26
+ # - rules: GnipApi::PowerTrack::Rule object
27
+ def create rules
28
+ raise GnipApi::Errors::PowerTrack::MissingRules.new if rules.nil? || rules.empty?
29
+ request = create_post_request(construct_rules(rules))
30
+ response = adapter.post(request)
31
+ return true if response.nil?
32
+ return GnipApi::JsonParser.new.parse(response)
33
+ end
34
+
35
+ # Deletes the specified rule. Parameters:
36
+ # - rules: GnipApi::PowerTrack::Rule object
37
+ def delete rules
38
+ raise GnipApi::Errors::PowerTrack::MissingRules.new if rules.nil? || rules.empty?
39
+ request = create_delete_request(construct_rules(rules))
40
+ response = adapter.delete(request)
41
+ return true if response.nil?
42
+ return GnipApi::JsonParser.new.parse(response)
43
+ end
44
+
45
+ # Parses an array of GnipApi::PowerTrack::Rule objects
46
+ # to the necesary JSON format for the endpoint
47
+ def construct_rules rules
48
+ parsed_rules = {:rules => []}
49
+ rules.each do |rule|
50
+ parsed_rules[:rules] << rule.attributes
51
+ end
52
+ parsed_rules.to_json
53
+ end
54
+
55
+ def parse_rules data
56
+ parsed_data = GnipApi::JsonParser.new.parse(data)
57
+ parsed_data['rules'].map{|rule| GnipApi::PowerTrack::Rule.new(:value => rule['value'], :tag => rule['tag'], :id => rule['id'])}
58
+ end
59
+
60
+ private
61
+ def endpoint
62
+ GnipApi::Endpoints.powertrack_rules(@label)
63
+ end
64
+
65
+ def create_get_request
66
+ GnipApi::Request.new_get(endpoint)
67
+ end
68
+
69
+ def create_post_request payload
70
+ GnipApi::Request.new_post(endpoint, payload)
71
+ end
72
+
73
+ def create_delete_request payload
74
+ delete_url = endpoint
75
+ delete_url.query = '_method=delete'
76
+ GnipApi::Request.new_delete(delete_url, payload)
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,122 @@
1
+ module GnipApi
2
+ module PowerTrack
3
+ class Stream
4
+ attr_reader :adapter
5
+
6
+ def initialize params = {}
7
+ @stream = params[:stream] || GnipApi.config.label
8
+ @user = params[:user] || GnipApi.configuration.user
9
+ @password = params[:password] || GnipApi.configuration.password
10
+ @account = params[:account] || GnipApi.configuration.account
11
+ @adapter = GnipApi::Adapter.new
12
+ @buffer = GnipApi::PowerTrack::Buffer.new
13
+ @running = false
14
+ end
15
+
16
+ def logger
17
+ GnipApi.logger
18
+ end
19
+
20
+ # Consumes the stream using a streamer thread instead of a simple block.
21
+ # This way the streamer can fill in the buffer and the block consumes it periodically.
22
+ def thread_consume
23
+ streamer = Thread.new do
24
+ logger.info "Starting streamer Thread"
25
+ begin
26
+ read_stream
27
+ ensure
28
+ logger.warn "Streamer exited"
29
+ end
30
+ end
31
+
32
+ begin
33
+ loop do
34
+ logger.warn "Streamer is down" unless streamer.alive?
35
+ raise GnipApi::Errors::PowerTrack::StreamDown unless streamer.alive?
36
+ entries = @buffer.read!
37
+ entries.any? ? yield(process_entries(entries)) : sleep(0.1)
38
+ end
39
+ ensure
40
+ streamer.kill if streamer.alive?
41
+ end
42
+ end
43
+
44
+ def consume
45
+ read_stream do |data|
46
+ yield(process_entries(data))
47
+ end
48
+ end
49
+
50
+ def consume_raw
51
+ read_stream do |data|
52
+ yield(data)
53
+ end
54
+ end
55
+
56
+ def consume_json
57
+ read_stream do |data|
58
+ yield(data.map{|item| parse_json(item)})
59
+ end
60
+ end
61
+
62
+ def read_stream
63
+ request = create_request
64
+ logger.info "Opening PowerTrack parsed stream"
65
+ begin
66
+ adapter.stream_get request do |chunk|
67
+ stream_running!
68
+ @buffer.insert! chunk
69
+ yield @buffer.read! if block_given?
70
+ end
71
+ ensure
72
+ logger.warn "Closing stream"
73
+ @running = false
74
+ end
75
+ end
76
+
77
+ def process_entries entries
78
+ logger.debug "PowerTrack Stream: #{entries.size} items received"
79
+ data = entries.map{|e| parse_json(e)}.compact
80
+ data.map!{|e| build_message(e)}
81
+ data.select(&:system_message?).each(&:log!)
82
+ return data
83
+ end
84
+
85
+ def build_message params
86
+ Gnip::Message.build(params)
87
+ end
88
+
89
+ def parse_json json
90
+ begin
91
+ GnipApi::JsonParser.new.parse json
92
+ rescue GnipApi::Errors::JsonParser::ParseError
93
+ nil
94
+ end
95
+ end
96
+
97
+ private
98
+ def stream_running! buffer=nil, chunk=nil
99
+ unless @running
100
+ logger.info "PowerTrack stream open"
101
+ @running = true
102
+ end
103
+ raise GnipApi::Errors::PowerTrack::BufferTooBig if buffer.over_limit?
104
+ logger.warn "PowerTrack Stream: Buffer size is growing too big (slow consuming)" if buffer.size > 65536
105
+ logger.debug "PowerTrack Stream: Received chunk of #{chunk.size} bytes" if chunk
106
+ logger.debug "PowerTrack Stream: #{buffer.size} bytes in buffer" if buffer
107
+ end
108
+
109
+ def create_request
110
+ headers = {}
111
+ headers['Accept-Encoding'] = 'gzip' if GnipApi.config.enable_gzip
112
+ headers['Accept-Encoding'] ||= 'json'
113
+ GnipApi::Request.new_get(endpoint, headers)
114
+ end
115
+
116
+ def endpoint
117
+ GnipApi::Endpoints.powertrack_stream(@stream)
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -4,7 +4,7 @@ module GnipApi
4
4
 
5
5
  class << self
6
6
  def new_get uri, headers=nil
7
- new(:uri => uri, :headers => headers, :request_method => GnipApi::Adapter::GET, )
7
+ new(:uri => uri, :headers => headers, :request_method => GnipApi::Adapter::GET)
8
8
  end
9
9
 
10
10
  def new_post uri, payload, headers=nil
@@ -22,5 +22,12 @@ module GnipApi
22
22
  @headers = params[:headers]
23
23
  @request_method = params[:request_method]
24
24
  end
25
+
26
+ def log!
27
+ GnipApi.logger.info "Starting #{request_method} request to #{uri}"
28
+ GnipApi.logger.debug "Headers -> #{headers.inspect}"
29
+ GnipApi.logger.debug "Payload -> #{payload.inspect}"
30
+ end
31
+
25
32
  end
26
33
  end