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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +16 -37
- data/lib/gnip_api/adapter.rb +42 -28
- data/lib/gnip_api/configuration.rb +4 -6
- data/lib/gnip_api/errors.rb +3 -0
- data/lib/gnip_api/gnip/gnip_data.rb +1 -1
- data/lib/gnip_api/gnip/system_message.rb +4 -0
- data/lib/gnip_api/power_track/buffer.rb +42 -0
- data/lib/gnip_api/power_track/rule.rb +77 -0
- data/lib/gnip_api/power_track/rule_validator.rb +6 -0
- data/lib/gnip_api/power_track/rules.rb +81 -0
- data/lib/gnip_api/power_track/stream.rb +122 -0
- data/lib/gnip_api/request.rb +8 -1
- data/lib/gnip_api/response.rb +21 -0
- data/lib/gnip_api/search.rb +108 -0
- data/lib/gnip_api/version.rb +1 -1
- data/lib/gnip_api.rb +5 -7
- data/spec/fixtures/rule_value_examples.json +27 -0
- data/spec/gnip_api/adapter_spec.rb +0 -87
- data/spec/gnip_api/configuration_spec.rb +0 -15
- data/spec/gnip_api/{apis/power_track → power_track}/buffer_spec.rb +2 -2
- data/spec/gnip_api/power_track/rule_spec.rb +89 -0
- data/spec/gnip_api/{apis/power_track → power_track}/rules_spec.rb +8 -8
- data/spec/gnip_api/power_track/stream_spec.rb +53 -0
- data/spec/gnip_api/response_spec.rb +27 -0
- data/spec/gnip_api/{apis/search_spec.rb → search_spec.rb} +9 -9
- data/spec/spec_helper.rb +4 -3
- metadata +20 -26
- data/lib/gnip_api/adapters/base_adapter.rb +0 -94
- data/lib/gnip_api/adapters/httparty_adapter.rb +0 -65
- data/lib/gnip_api/apis/power_track/buffer.rb +0 -38
- data/lib/gnip_api/apis/power_track/rule.rb +0 -33
- data/lib/gnip_api/apis/power_track/rule_validator.rb +0 -8
- data/lib/gnip_api/apis/power_track/rules.rb +0 -83
- data/lib/gnip_api/apis/power_track/stream.rb +0 -84
- data/lib/gnip_api/apis/search.rb +0 -109
- data/spec/gnip_api/adapters/base_adapter_spec.rb +0 -0
- data/spec/gnip_api/adapters/httparty_adapter_spec.rb +0 -0
- data/spec/gnip_api/apis/power_track/rule_spec.rb +0 -62
- data/spec/gnip_api/apis/power_track/stream_spec.rb +0 -93
- data/spec/lib/test_adapter.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 515a1f5c48c72c4f23dfc7e4882d6f7b8b47067c
|
4
|
+
data.tar.gz: f4775d00111fb0811aefb798aa178357927ccf02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 358bd8b2aba8653f782a747793b1f4580712ef74c3b188ef9a5fd65dfb8cd5cfeab1d8238327a147b42a9226abc925bab2999bb83c74f929abb917fe0a9c0ebd
|
7
|
+
data.tar.gz: 89bd54df5d5e31a04f467996e104651c4ace873de6748a96d33788eaf89e01d022aa26f3c81f8261998a138346f9ebcdc192ca2c5f5426267cbc7502bad0df10
|
data/Gemfile.lock
CHANGED
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
|
-
-
|
10
|
-
-
|
11
|
-
-
|
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::
|
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::
|
89
|
+
results = GnipApi::Search.new.counts :rule => rule
|
93
90
|
```
|
94
91
|
|
95
92
|
For activities:
|
96
93
|
|
97
94
|
```ruby
|
98
|
-
results = GnipApi::
|
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::
|
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::
|
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::
|
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::
|
128
|
-
rules << GnipApi::
|
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::
|
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::
|
139
|
+
GnipApi::PowerTrack::Rules.new.list
|
143
140
|
```
|
144
141
|
|
145
|
-
That will return an array of GnipRule::
|
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::
|
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::
|
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.
|
data/lib/gnip_api/adapter.rb
CHANGED
@@ -4,62 +4,76 @@ module GnipApi
|
|
4
4
|
POST = 'POST'
|
5
5
|
DELETE = 'DELETE'
|
6
6
|
|
7
|
-
attr_reader :
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
data/lib/gnip_api/errors.rb
CHANGED
@@ -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::
|
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
|
@@ -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,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
|
data/lib/gnip_api/request.rb
CHANGED
@@ -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
|