datasift 2.1.1 → 3.0.0.beta
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/.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
|