datasift 2.1.1 → 3.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +100 -0
- data/Gemfile.lock +32 -0
- data/README.md +38 -79
- data/VERSION +1 -1
- data/datasift.gemspec +21 -24
- data/examples/auth.rb +44 -0
- data/examples/core_api_eg.rb +46 -0
- data/examples/historics_eg.rb +50 -0
- data/examples/historics_preview_eg.rb +30 -0
- data/examples/live_stream_eg.rb +89 -0
- data/examples/managed_source_eg.rb +56 -0
- data/examples/pull.rb +44 -0
- data/examples/push_eg.rb +56 -0
- data/lib/api/api_resource.rb +23 -0
- data/lib/datasift.rb +287 -14
- data/lib/errors.rb +59 -0
- data/lib/historics.rb +76 -0
- data/lib/historics_preview.rb +20 -0
- data/lib/live_stream.rb +53 -0
- data/lib/managed_source.rb +57 -0
- data/lib/push.rb +156 -0
- data/tests/core_api_test.rb +42 -0
- metadata +51 -73
- data/Rakefile +0 -34
- data/config.yml +0 -2
- data/examples/consume-stream.rb +0 -63
- data/examples/deletes.rb +0 -52
- data/examples/dpu.rb +0 -115
- data/examples/football-buffered.rb +0 -51
- data/examples/football.rb +0 -53
- data/examples/historics.sh +0 -2
- data/examples/historics/create-from-csdl.rb +0 -71
- data/examples/historics/create-from-hash.rb +0 -65
- data/examples/historics/delete.rb +0 -30
- data/examples/historics/env.rb +0 -37
- data/examples/historics/list.rb +0 -30
- data/examples/historics/start.rb +0 -30
- data/examples/historics/stop.rb +0 -30
- data/examples/historics/view.rb +0 -28
- data/examples/push.sh +0 -2
- data/examples/push/delete.rb +0 -33
- data/examples/push/env.rb +0 -53
- data/examples/push/list.rb +0 -30
- data/examples/push/pause.rb +0 -33
- data/examples/push/push-from-hash.rb +0 -72
- data/examples/push/push-historic-from-csdl.rb +0 -98
- data/examples/push/push-stream-from-csdl.rb +0 -70
- data/examples/push/resume.rb +0 -33
- data/examples/push/stop.rb +0 -33
- data/examples/push/view-log.rb +0 -45
- data/examples/push/view.rb +0 -31
- data/examples/twitter-track.rb +0 -61
- data/lib/DataSift/apiclient.rb +0 -73
- data/lib/DataSift/definition.rb +0 -202
- data/lib/DataSift/exceptions.rb +0 -33
- data/lib/DataSift/historic.rb +0 -316
- data/lib/DataSift/managed_source.rb +0 -263
- data/lib/DataSift/mockapiclient.rb +0 -44
- data/lib/DataSift/push_definition.rb +0 -115
- data/lib/DataSift/push_subscription.rb +0 -330
- data/lib/DataSift/stream_consumer.rb +0 -166
- data/lib/DataSift/stream_consumer_http.rb +0 -188
- data/lib/DataSift/user.rb +0 -311
- data/test/helper.rb +0 -95
- data/test/test_definition.rb +0 -273
- data/test/test_historics.rb +0 -233
- data/test/test_pushdefinition.rb +0 -92
- data/test/test_pushsubscription.rb +0 -17
- data/test/test_user.rb +0 -130
- data/test/testdata.yml +0 -30
@@ -0,0 +1,30 @@
|
|
1
|
+
require './auth'
|
2
|
+
class HistoricsPreviewApi < DataSiftExample
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
run
|
6
|
+
end
|
7
|
+
|
8
|
+
def run
|
9
|
+
begin
|
10
|
+
puts 'Creating hash'
|
11
|
+
stream = @datasift.compile 'interaction.content contains "datasift"'
|
12
|
+
hash = stream[:data][:hash]
|
13
|
+
|
14
|
+
puts 'Creating a preview'
|
15
|
+
# see http://dev.datasift.com/docs/rest-api/previewcreate for docs
|
16
|
+
parameters = 'interaction.author.link,targetVol,hour;interaction.type,freqDist,10'
|
17
|
+
start = Time.now.to_i - (3600 * 48) # 48hrs ago
|
18
|
+
source = @datasift.historics_preview.create(hash, parameters, start)
|
19
|
+
puts source
|
20
|
+
|
21
|
+
puts 'Getting preview data'
|
22
|
+
puts @datasift.historics_preview.get source[:data][:id]
|
23
|
+
|
24
|
+
rescue DataSiftError => dse
|
25
|
+
puts dse.message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
HistoricsPreviewApi.new
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require './auth'
|
2
|
+
class StreamingApi < DataSiftExample
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
@datasift = DataSift::Client.new(@config)
|
7
|
+
run
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
begin
|
12
|
+
rubyReceived = 0
|
13
|
+
pythonReceived = 0
|
14
|
+
ruby = 'interaction.content contains "ruby"'
|
15
|
+
rubyStream = @datasift.compile ruby
|
16
|
+
|
17
|
+
python = 'interaction.content contains "python"'
|
18
|
+
pythonStream = @datasift.compile python
|
19
|
+
|
20
|
+
on_delete = lambda { |stream, m| puts 'We must delete this to be compliant ==> ' + m }
|
21
|
+
|
22
|
+
on_error = lambda do |stream, e|
|
23
|
+
puts 'A serious error has occurred'
|
24
|
+
puts e.message
|
25
|
+
end
|
26
|
+
|
27
|
+
on_message_ruby = lambda do |message, stream, hash|
|
28
|
+
rubyReceived += 1
|
29
|
+
puts "Ruby #{rubyReceived}, #{message}"
|
30
|
+
|
31
|
+
if rubyReceived >= 10
|
32
|
+
puts 'un-subscribing from ruby stream '+ hash
|
33
|
+
stream.unsubscribe hash
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
on_message_python = lambda do |message, stream, hash|
|
38
|
+
pythonReceived += 1
|
39
|
+
puts "python #{pythonReceived}, #{message}"
|
40
|
+
|
41
|
+
if pythonReceived >= 10
|
42
|
+
puts 'un-subscribing from python stream '+ hash
|
43
|
+
stream.unsubscribe hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
on_connect = lambda do |stream|
|
48
|
+
#
|
49
|
+
puts 'subscribing to python stream '+ pythonStream[:data][:hash]
|
50
|
+
stream.subscribe(pythonStream[:data][:hash], on_message_python)
|
51
|
+
puts 'Subscribed to '+ pythonStream[:data][:hash]
|
52
|
+
sleep 1
|
53
|
+
#
|
54
|
+
puts 'subscribing to ruby stream '+ rubyStream[:data][:hash]
|
55
|
+
stream.subscribe(rubyStream[:data][:hash], on_message_ruby)
|
56
|
+
puts 'Subscribed to '+ rubyStream[:data][:hash]
|
57
|
+
end
|
58
|
+
|
59
|
+
on_close = lambda do |stream|
|
60
|
+
puts 'closed'
|
61
|
+
end
|
62
|
+
|
63
|
+
on_datasift_message = lambda do |stream, message, hash|
|
64
|
+
#not all messages have a hash
|
65
|
+
puts "is_success = #{message[:is_success]}, is_failure = #{message[:is_failure]}, is_warning = #{message[:is_warning]}, is_tick = #{message[:is_tick]}"
|
66
|
+
puts "DataSift Message #{hash} ==> #{message}"
|
67
|
+
end
|
68
|
+
|
69
|
+
conn = DataSift::new_stream(@config, on_delete, on_error, on_connect, on_close)
|
70
|
+
conn.on_datasift_message = on_datasift_message
|
71
|
+
#can do something else here now...
|
72
|
+
puts 'Do some other business stuff...'
|
73
|
+
conn.stream.read_thread.join
|
74
|
+
#rescue DataSiftError
|
75
|
+
rescue DataSiftError => dse
|
76
|
+
puts "Error #{dse.message}"
|
77
|
+
# Then match specific one to take action - All errors thrown by the client extend DataSiftError
|
78
|
+
case dse
|
79
|
+
when ConnectionError
|
80
|
+
# some connection error
|
81
|
+
when AuthError
|
82
|
+
when BadRequestError
|
83
|
+
else
|
84
|
+
# do something else...
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
StreamingApi.new
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require './auth'
|
2
|
+
class ManagedSourceApi < DataSiftExample
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
run
|
6
|
+
end
|
7
|
+
|
8
|
+
def run
|
9
|
+
begin
|
10
|
+
puts 'Creating a managed source'
|
11
|
+
parameters = {:likes => true,
|
12
|
+
:posts_by_others => true,
|
13
|
+
:comments => true
|
14
|
+
}
|
15
|
+
resources = [{
|
16
|
+
:parameters => {
|
17
|
+
:url => 'http://www.facebook.com/thegaurdian',
|
18
|
+
:title => 'Some news page',
|
19
|
+
:id => :thegaurdian
|
20
|
+
}
|
21
|
+
}]
|
22
|
+
auth = [{
|
23
|
+
:parameters => {
|
24
|
+
:value => 'CAAIUKbXn8xsBAN5MnINICUT9gEBsZBh3hKoSEeIMP0ZA4zadMr64X6ljvZC4VBZCyYr9tyhih5nO0R39A1FQ848v0mZA6d3ehIHuSbKb7avtfLOtL5XKDYRIXHmRWreyxxVc3jk7CIa4ZCI5AAKeUUO3GUS8EaPdYVh9rO5FvvNmIatzz6k8el'
|
25
|
+
}
|
26
|
+
}]
|
27
|
+
|
28
|
+
source = @datasift.managed_source.create('facebook_page', 'My managed source', parameters, resources, auth)
|
29
|
+
puts source
|
30
|
+
|
31
|
+
id = source[:data][:id]
|
32
|
+
|
33
|
+
puts 'Starting delivery for my private source'
|
34
|
+
puts @datasift.managed_source.start id
|
35
|
+
|
36
|
+
puts 'Updating'
|
37
|
+
puts @datasift.managed_source.update(id, 'facebook_page', 'Updated source', parameters, resources, auth)
|
38
|
+
|
39
|
+
puts 'Getting info from DataSift about my page'
|
40
|
+
puts @datasift.managed_source.get id
|
41
|
+
|
42
|
+
puts 'Fetching logs'
|
43
|
+
puts @datasift.managed_source.log id
|
44
|
+
|
45
|
+
puts 'Stopping'
|
46
|
+
puts @datasift.managed_source.stop id
|
47
|
+
|
48
|
+
puts 'Deleting'
|
49
|
+
puts @datasift.managed_source.delete id
|
50
|
+
rescue DataSiftError => dse
|
51
|
+
puts dse.message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
ManagedSourceApi.new
|
data/examples/pull.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require './auth'
|
2
|
+
class PushApi < DataSiftExample
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
begin
|
9
|
+
@params = {:output_type => 'pull'}
|
10
|
+
puts 'Validating'
|
11
|
+
if @datasift.push.valid? @params
|
12
|
+
stream = @datasift.compile 'interaction.content contains "music"'
|
13
|
+
subscription = create_push(stream[:data][:hash])
|
14
|
+
|
15
|
+
subscription_id = subscription[:data][:id]
|
16
|
+
# Pull interactions from the push queue - this will only work if we have set
|
17
|
+
# the Push Subscription output_type above to 'pull'
|
18
|
+
|
19
|
+
puts 'Waiting...'
|
20
|
+
sleep 10
|
21
|
+
|
22
|
+
# Passing a lambda is more efficient because it is executed once for each interaction received
|
23
|
+
# this saves having to iterate over the array returned so the same iteration isn't done twice
|
24
|
+
puts 'Pulling'
|
25
|
+
@datasift.push.pull(subscription_id, 20971520, '', lambda{ |e| puts "on_message => #{e}" })
|
26
|
+
|
27
|
+
puts 'Waiting...'
|
28
|
+
sleep 10
|
29
|
+
puts 'Pulling'
|
30
|
+
@datasift.push.pull(subscription_id, 20971520, '', lambda{ |e| puts "on_message => #{e}" })
|
31
|
+
|
32
|
+
puts 'Stop Subscription'
|
33
|
+
@datasift.push.stop subscription_id
|
34
|
+
|
35
|
+
puts 'Delete Subscription'
|
36
|
+
@datasift.push.delete subscription_id
|
37
|
+
end
|
38
|
+
rescue DataSiftError => dse
|
39
|
+
puts dse
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
PushApi.new().run
|
data/examples/push_eg.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require './auth'
|
2
|
+
class PushApi < DataSiftExample
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
begin
|
9
|
+
puts 'Validating'
|
10
|
+
if @datasift.push.valid? @params
|
11
|
+
stream = @datasift.compile 'interaction.content contains "datasift"'
|
12
|
+
subscription = create_push(stream[:data][:hash])
|
13
|
+
|
14
|
+
subscription_id = subscription[:data][:id]
|
15
|
+
#pull a bunch of interactions from the push queue - only work if we had set the output_type above to pull
|
16
|
+
#pull @datasift.pull subscription_id
|
17
|
+
|
18
|
+
puts 'updating subscription'
|
19
|
+
# update the info we just used to create
|
20
|
+
# id, name and output_params.* are valid
|
21
|
+
puts @datasift.push.update @params.merge({:id => subscription_id, :name => 'My updated awesome name'})
|
22
|
+
|
23
|
+
puts 'getting subscription info'
|
24
|
+
# get details for a subscription also available are
|
25
|
+
# push.[get, get_by_hash,get_by_historics_id]
|
26
|
+
puts @datasift.push.get_by_subscription subscription_id
|
27
|
+
|
28
|
+
puts 'getting logs for subscription'
|
29
|
+
# get log messages for a subscription id
|
30
|
+
#also available push.logs to fetch logs for all subscriptions
|
31
|
+
puts @datasift.push.logs_for subscription_id
|
32
|
+
|
33
|
+
puts 'pausing subscription'
|
34
|
+
#pause the subscription that was created
|
35
|
+
puts @datasift.push.pause subscription_id
|
36
|
+
|
37
|
+
puts 'resuming subscription'
|
38
|
+
# resume the subscription that was just paused
|
39
|
+
puts @datasift.push.resume subscription_id
|
40
|
+
|
41
|
+
puts 'stopping subscription'
|
42
|
+
# stop the subscription
|
43
|
+
puts @datasift.push.stop subscription_id
|
44
|
+
|
45
|
+
puts 'deleting subscription'
|
46
|
+
#and delete it
|
47
|
+
puts @datasift.push.delete subscription_id
|
48
|
+
end
|
49
|
+
#rescue DataSiftError
|
50
|
+
rescue DataSiftError => dse
|
51
|
+
puts dse.message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
PushApi.new().run
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DataSift
|
2
|
+
class ApiResource
|
3
|
+
include DataSift
|
4
|
+
|
5
|
+
def initialize (config)
|
6
|
+
@config = config
|
7
|
+
config[:api_host] = 'api.datasift.com' unless config.has_key?(:api_host)
|
8
|
+
config[:stream_host] = 'websocket.datasift.com' unless config.has_key?(:stream_host)
|
9
|
+
config[:api_version] = 'v1' unless config.has_key?(:api_version)
|
10
|
+
config[:enable_ssl] = true unless config.has_key?(:enable_ssl)
|
11
|
+
# max 320 seconds retry - http://dev.datasift.com/docs/streaming-api/reconnecting
|
12
|
+
config[:max_retry_time] = 320 unless config.has_key?(:max_retry_time)
|
13
|
+
end
|
14
|
+
|
15
|
+
def requires params
|
16
|
+
params.each { |k, v|
|
17
|
+
if v == nil || v.to_s.length == 0
|
18
|
+
raise InvalidParamError.new "#{k} is a required parameter, it cannot be nil or empty"
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/datasift.rb
CHANGED
@@ -1,15 +1,288 @@
|
|
1
|
-
#This is the base file for the DataSift API library. Require this file to get
|
2
|
-
#access to the full library functionality.
|
3
|
-
require 'rubygems'
|
4
|
-
|
5
1
|
dir = File.dirname(__FILE__)
|
6
|
-
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
|
12
|
-
require dir + '/
|
13
|
-
|
14
|
-
require dir + '/
|
15
|
-
require dir + '/
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'rest_client'
|
5
|
+
require 'multi_json'
|
6
|
+
require 'websocket_td'
|
7
|
+
|
8
|
+
require dir + '/api/api_resource'
|
9
|
+
|
10
|
+
require dir + '/errors'
|
11
|
+
require dir + '/push'
|
12
|
+
require dir + '/historics'
|
13
|
+
require dir + '/historics_preview'
|
14
|
+
require dir + '/managed_source'
|
15
|
+
require dir + '/live_stream'
|
16
|
+
|
17
|
+
require 'rbconfig'
|
18
|
+
|
19
|
+
module DataSift
|
20
|
+
#
|
21
|
+
IS_WINDOWS = (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
|
22
|
+
VERSION = File.open('../VERSION').first
|
23
|
+
|
24
|
+
class Client < ApiResource
|
25
|
+
|
26
|
+
#+config+:: A hash containing configuration options for the client for e.g.
|
27
|
+
# {:username => 'some_user', :api_key => 'ds_api_key', :enable_ssl => true, :open_timeout => 30, :timeout => 30}
|
28
|
+
def initialize (config)
|
29
|
+
if config == nil
|
30
|
+
raise InvalidConfigError.new ('Config cannot be nil')
|
31
|
+
end
|
32
|
+
if config.key?(:api_key) == false || config.key?(:username) == false
|
33
|
+
raise InvalidConfigError.new('A valid username and API key are required')
|
34
|
+
end
|
35
|
+
|
36
|
+
@config = config
|
37
|
+
@historics = DataSift::Historics.new(config)
|
38
|
+
@push = DataSift::Push.new(config)
|
39
|
+
@managed_source = DataSift::ManagedSource.new(config)
|
40
|
+
@historics_preview = DataSift::HistoricsPreview.new(config)
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :historics, :push, :managed_source, :historics_preview
|
44
|
+
|
45
|
+
##
|
46
|
+
# Checks if the syntax of the given CSDL is valid
|
47
|
+
def valid?(csdl)
|
48
|
+
requires({:csdl => csdl})
|
49
|
+
res= DataSift.request(:POST, 'validate', @config, {:csdl => csdl})
|
50
|
+
res[:http][:status] == 200
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Compile CSDL code.
|
55
|
+
#+csdl+:: The CSDL you wish to compile
|
56
|
+
def compile(csdl)
|
57
|
+
requires({:csdl => csdl})
|
58
|
+
DataSift.request(:POST, 'compile', @config, {:csdl => csdl})
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Check the number of objects processed and delivered for a given time period.
|
63
|
+
#+period+:: Can be "day", "hour", or "current", defaults to hour
|
64
|
+
def usage(period = :hour)
|
65
|
+
DataSift.request(:POST, 'usage', @config, {:period => period})
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Calculate the DPU cost of consuming a stream.
|
70
|
+
def dpu(hash)
|
71
|
+
requires ({:hash => hash})
|
72
|
+
DataSift.request(:POST, 'dpu', @config, {:hash => hash})
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Determine your credit balance or DPU balance.
|
77
|
+
def balance
|
78
|
+
DataSift.request(:POST, 'balance', @config, {})
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Collect a batch of interactions from a push queue
|
83
|
+
def pull(id, size = 20971520, cursor='')
|
84
|
+
DataSift.request(:POST, 'pull', @config, {:id => id, :size => size, :cursor => cursor})
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# Generates and executes an HTTP request from the params provided
|
91
|
+
# Params:
|
92
|
+
# +method+:: the HTTP method to use e.g. GET,POST
|
93
|
+
# +path+:: the DataSift path relevant to the base URL of the API
|
94
|
+
# +username+:: API username
|
95
|
+
# +api_key+:: DS api key
|
96
|
+
# +params+:: A hash representing the params to use in the request, if it's a get,head or delete request these params
|
97
|
+
# are used as query string params, if not they become form url encoded params
|
98
|
+
# +headers+:: any headers to pass to the API, Authorization header is automatically included
|
99
|
+
def self.request(method, path, config, params = {}, headers = {}, timeout=30, open_timeout=30, new_line_separated=false)
|
100
|
+
validate config
|
101
|
+
options = {}
|
102
|
+
url = build_url(path, config)
|
103
|
+
case method.to_s.downcase.to_sym
|
104
|
+
when :get, :head, :delete
|
105
|
+
url += "#{URI.parse(url).query ? '&' : '?'}#{encode params}"
|
106
|
+
payload = nil
|
107
|
+
else
|
108
|
+
payload = encode params
|
109
|
+
end
|
110
|
+
|
111
|
+
headers.update ({
|
112
|
+
:user_agent => "DataSift/#{config[:api_version]} Ruby/v#{VERSION}",
|
113
|
+
:authorization => "#{config[:username]}:#{config[:api_key]}",
|
114
|
+
:content_type => 'application/x-www-form-urlencoded'
|
115
|
+
})
|
116
|
+
|
117
|
+
options.update(
|
118
|
+
:headers => headers,
|
119
|
+
:method => method,
|
120
|
+
:open_timeout => open_timeout,
|
121
|
+
:timeout => timeout,
|
122
|
+
:payload => payload,
|
123
|
+
:url => url
|
124
|
+
)
|
125
|
+
|
126
|
+
begin
|
127
|
+
response = RestClient::Request.execute options
|
128
|
+
if response != nil && response.length > 0
|
129
|
+
if new_line_separated
|
130
|
+
res_arr = response.split("\n")
|
131
|
+
data = []
|
132
|
+
res_arr.each { |e|
|
133
|
+
interaction = MultiJson.load(e, :symbolize_keys => true)
|
134
|
+
data.push(interaction)
|
135
|
+
if params.has_key? :on_interaction
|
136
|
+
params[:on_interaction].call(interaction)
|
137
|
+
end
|
138
|
+
}
|
139
|
+
else
|
140
|
+
data = MultiJson.load(response, :symbolize_keys => true)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
data = {}
|
144
|
+
end
|
145
|
+
{
|
146
|
+
:data => data,
|
147
|
+
:datasift => {
|
148
|
+
:x_ratelimit_limit => response.headers[:x_ratelimit_limit],
|
149
|
+
:x_ratelimit_remaining => response.headers[:x_ratelimit_remaining],
|
150
|
+
:x_ratelimit_cost => response.headers[:x_ratelimit_cost]
|
151
|
+
},
|
152
|
+
:http => {
|
153
|
+
:status => response.code,
|
154
|
+
:headers => response.headers
|
155
|
+
}
|
156
|
+
}
|
157
|
+
rescue MultiJson::DecodeError => de
|
158
|
+
raise DataSiftError.new response
|
159
|
+
rescue SocketError => e
|
160
|
+
process_client_error(e)
|
161
|
+
rescue RestClient::ExceptionWithResponse => e
|
162
|
+
begin
|
163
|
+
code = e.http_code
|
164
|
+
body = e.http_body
|
165
|
+
if code && body
|
166
|
+
error = MultiJson.load(body)
|
167
|
+
handle_api_error(e.http_code, error['error'] + " for URL #{url}")
|
168
|
+
else
|
169
|
+
process_client_error(e)
|
170
|
+
end
|
171
|
+
rescue MultiJson::DecodeError
|
172
|
+
process_client_error(e)
|
173
|
+
end
|
174
|
+
rescue RestClient::Exception, Errno::ECONNREFUSED => e
|
175
|
+
process_client_error (e)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.build_url(path, config)
|
180
|
+
'http' + (config[:enable_ssl] ? 's' : '') + '://' + config[:api_host] + '/' + config[:api_version] + '/' + path
|
181
|
+
end
|
182
|
+
|
183
|
+
#returns true if username and api key are set
|
184
|
+
def self.is_invalid? config
|
185
|
+
!config.key?(:username) || !config.key?(:api_key)
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.validate conf
|
189
|
+
if is_invalid? conf
|
190
|
+
raise InvalidConfigError.new 'A username and api_key are required'
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def self.encode params
|
195
|
+
URI.escape(params.collect { |k, v| "#{k}=#{v}" }.join('&'))
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.handle_api_error(code, body)
|
199
|
+
case code
|
200
|
+
when 400
|
201
|
+
raise BadRequestError.new(code, body)
|
202
|
+
when 401
|
203
|
+
raise AuthError.new(code, body)
|
204
|
+
when 404
|
205
|
+
raise ApiResourceNotFoundError.new(code, body)
|
206
|
+
else
|
207
|
+
raise DataSiftError.new(code, body)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.process_client_error(e)
|
212
|
+
case e
|
213
|
+
when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
|
214
|
+
message = 'Unable to connect to DataSift. Please check your connection and try again'
|
215
|
+
when RestClient::SSLCertificateNotVerified
|
216
|
+
message = 'Failed to complete SSL verification'
|
217
|
+
when SocketError
|
218
|
+
message = 'Communication with DataSift failed. Are you able to resolve api.datasift.com?'
|
219
|
+
else
|
220
|
+
message = 'Unexpected error.'
|
221
|
+
end
|
222
|
+
raise ConnectionError.new(message + " (Network error: #{e.message})")
|
223
|
+
end
|
224
|
+
|
225
|
+
##
|
226
|
+
# a Proc/lambda callback to receive delete messages
|
227
|
+
# DataSift and its customers are required to process Twitter's delete request, a delete handler must be provided
|
228
|
+
# a Proc/lambda callback to receive errors
|
229
|
+
# Because EventMachine is used errors can be raised from another thread, this method will receive any such errors
|
230
|
+
def self.new_stream(config, on_delete, on_error, on_open = nil, on_close = nil)
|
231
|
+
if on_delete == nil || on_error == nil
|
232
|
+
raise NotConfiguredError.new 'on_delete and on_error are required before you can connect'
|
233
|
+
end
|
234
|
+
|
235
|
+
begin
|
236
|
+
stream = WebsocketTD::Websocket.new('websocket.datasift.com', '/multi', "username=#{config[:username]}&api_key=#{config[:api_key]}")
|
237
|
+
connection = LiveStream.new(config, stream)
|
238
|
+
|
239
|
+
stream.on_open = lambda {
|
240
|
+
connection.connected = true
|
241
|
+
connection.retry_timeout = 0
|
242
|
+
on_open.call(connection) if on_open != nil
|
243
|
+
}
|
244
|
+
|
245
|
+
stream.on_close = lambda {
|
246
|
+
connection.connected = false
|
247
|
+
retry_connect(config, connection, on_delete, on_error, on_open, on_close, '', true)
|
248
|
+
}
|
249
|
+
stream.on_error = lambda {
|
250
|
+
connection.connected = false
|
251
|
+
on_error.call(connection) if on_close != nil
|
252
|
+
retry_connect(config, connection, on_delete, on_error, on_open, on_close)
|
253
|
+
}
|
254
|
+
stream.on_message =lambda { |msg|
|
255
|
+
data = MultiJson.load(msg.data, :symbolize_keys => true)
|
256
|
+
if data.has_key?(:deleted)
|
257
|
+
on_delete.call(connection, data)
|
258
|
+
elsif data.has_key?(:status)
|
259
|
+
connection.fire_ds_message(data)
|
260
|
+
else
|
261
|
+
connection.fire_on_message(data[:hash], data[:data])
|
262
|
+
end
|
263
|
+
}
|
264
|
+
rescue Exception => e
|
265
|
+
retry_connect(config, connection, on_delete, on_error, on_open, on_close, e.message)
|
266
|
+
end
|
267
|
+
connection
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.retry_connect(config, connection, on_delete, on_error, on_open, on_close, message = '', use_closed = false)
|
271
|
+
connection.retry_timeout = connection.retry_timeout == 0 ? 10 : connection.retry_timeout * 2
|
272
|
+
if connection.retry_timeout > config[:max_retry_time]
|
273
|
+
if use_closed && on_close != nil
|
274
|
+
on_close.call(connection)
|
275
|
+
else
|
276
|
+
on_error.call ReconnectTimeoutError.new "Connecting to DataSift has failed, re-connection was attempted but
|
277
|
+
multiple consecutive failures where encountered. As a result no further
|
278
|
+
re-connection will be automatically attempted. Manually invoke connect() after
|
279
|
+
investigating the cause of the failure, be sure to observe DataSift's
|
280
|
+
re-connect policies available at http://dev.datasift.com/docs/streaming-api/reconnecting
|
281
|
+
- Error { #{message}}"
|
282
|
+
end
|
283
|
+
else
|
284
|
+
sleep connection.retry_timeout
|
285
|
+
new_stream(config, on_delete, on_error, on_open, on_close)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|