voxcast_api 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/voxcast_api/base.rb +184 -0
- data/lib/voxcast_api/http.rb +75 -0
- data/lib/voxcast_api/purge.rb +184 -0
- data/lib/voxcast_api.rb +4 -0
- data/xml/log_list.xml +12 -0
- data/xml/log_list2.xml +10 -0
- data/xml/purge.xml +2 -0
- metadata +70 -0
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'set'
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'forwardable'
|
5
|
+
require 'hpricot'
|
6
|
+
|
7
|
+
class VoxcastApi::Base < VoxcastApi::Http
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@entry, :device_id
|
11
|
+
attr_accessor :key, :secret_key
|
12
|
+
|
13
|
+
def initialize(entry = nil)
|
14
|
+
super(entry)
|
15
|
+
if @entry
|
16
|
+
@key = entry.purge_key
|
17
|
+
@secret_key = entry.purge_secret_key
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def lookup_method(key)
|
22
|
+
# this will default to the new 1.0 api, but 0.1 can override it
|
23
|
+
case key.to_s
|
24
|
+
when 'devices'
|
25
|
+
'voxel.devices.list'
|
26
|
+
when 'test'
|
27
|
+
'voxel.test.echo'
|
28
|
+
when 'purge_site'
|
29
|
+
'voxel.voxcast.ondemand.content.purge_site'
|
30
|
+
when 'purge_file'
|
31
|
+
'voxel.voxcast.ondemand.content.purge_file'
|
32
|
+
when 'purge_dir'
|
33
|
+
'voxel.voxcast.ondemand.content.purge_directory'
|
34
|
+
when 'populate'
|
35
|
+
'voxel.voxcast.ondemand.content.populate'
|
36
|
+
when 'status'
|
37
|
+
'voxel.voxcast.ondemand.content.transaction_status'
|
38
|
+
when 'auth_key'
|
39
|
+
'voxel.hapi.authkeys.read'
|
40
|
+
when 'log_list'
|
41
|
+
'voxel.voxcast.ondemand.logs.list'
|
42
|
+
when 'log_download'
|
43
|
+
'voxel.voxcast.ondemand.logs.download'
|
44
|
+
when 'live_log_list'
|
45
|
+
'voxel.voxcast.live.logs.list'
|
46
|
+
when 'live_log_download'
|
47
|
+
'voxel.voxcast.live.logs.download'
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test(params = {})
|
54
|
+
execute_method(:test, params)
|
55
|
+
end
|
56
|
+
|
57
|
+
def populate(paths)
|
58
|
+
params = { 'device_id' => device_id,
|
59
|
+
'paths' => get_urls(paths)
|
60
|
+
}
|
61
|
+
execute(:populate, params)
|
62
|
+
end
|
63
|
+
|
64
|
+
def status(id)
|
65
|
+
execute_method(:status, {'transaction_id'=>id})
|
66
|
+
end
|
67
|
+
|
68
|
+
def devices
|
69
|
+
ret = execute_method(:devices)
|
70
|
+
devices = []
|
71
|
+
devices = ret['devices'] if ret
|
72
|
+
end
|
73
|
+
|
74
|
+
def log_list(params = {})
|
75
|
+
params[:complex_parser] = true
|
76
|
+
ret = execute_method(:log_list, params)
|
77
|
+
# parse the response with complex
|
78
|
+
return [] if ret[:status] != true
|
79
|
+
data = ret[:detail]
|
80
|
+
# get the log files
|
81
|
+
data.search('log_file')
|
82
|
+
end
|
83
|
+
|
84
|
+
def log_download(filename, dst_file)
|
85
|
+
# we just want the raw output, so skip the processing of the response and
|
86
|
+
# use the special params "raw"
|
87
|
+
params = {'filename' => filename, 'convert' => 'raw', :skip_process => true}
|
88
|
+
resp = execute_method(:log_download, params)
|
89
|
+
if resp.code.to_i == 200
|
90
|
+
File.open(dst_file, 'wb') {|f| f.write(resp.body)}
|
91
|
+
true
|
92
|
+
else
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def execute_method(method, params = {})
|
98
|
+
url = nil
|
99
|
+
params['method'] = lookup_method(method)
|
100
|
+
if method.to_s == 'auth_key'
|
101
|
+
# if its auth key, replace http with https
|
102
|
+
url = @url
|
103
|
+
url = url.gsub('http://', 'https://')
|
104
|
+
url = "https://#{url}" if url.index('https://').blank?
|
105
|
+
end
|
106
|
+
execute(url, params)
|
107
|
+
end
|
108
|
+
|
109
|
+
def api_sig(params, secret)
|
110
|
+
sig = secret.to_s
|
111
|
+
p = params.sort {|a, b| a[0].to_s <=> b[0].to_s}
|
112
|
+
p.each {|k, v| sig += k.to_s + v.to_s}
|
113
|
+
Digest::MD5.hexdigest(sig)
|
114
|
+
end
|
115
|
+
|
116
|
+
def auth_key?(params = {})
|
117
|
+
params['method'] == 'voxel.hapi.authkeys.read'
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_basic_auth(params = {})
|
121
|
+
return [@user, @password] if auth_key?(params)
|
122
|
+
super(params)
|
123
|
+
end
|
124
|
+
|
125
|
+
def get_path_and_params(params = {}, url = nil)
|
126
|
+
url = @url if url.blank?
|
127
|
+
uri = URI.parse(url)
|
128
|
+
get_params = {'method' => params['method']}
|
129
|
+
get_path = build_query_string(get_params)
|
130
|
+
path = "#{uri.path}?#{get_path}"
|
131
|
+
# return without params if its an authkey
|
132
|
+
return [path, ''] if auth_key?(params)
|
133
|
+
data = get_data(params)
|
134
|
+
[path, data]
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_data(params = {}, key = nil, secret_key = nil, key_label = 'key')
|
138
|
+
key = @key if key.blank?
|
139
|
+
secret_key = @secret_key if secret_key.blank?
|
140
|
+
@logger.warn('warning, the key or secret key is blank, purging might not work') if key.blank? || secret_key.blank?
|
141
|
+
params = params.merge({'timestamp' => timestamp(),
|
142
|
+
key_label => key})
|
143
|
+
sig = api_sig(params, secret_key)
|
144
|
+
params['api_sig'] = sig
|
145
|
+
data = build_query_string(params)
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_method(params = {})
|
149
|
+
return :get if auth_key?(params)
|
150
|
+
super(params)
|
151
|
+
end
|
152
|
+
|
153
|
+
def process_response(resp, complex = false)
|
154
|
+
if resp.code.to_i == 200
|
155
|
+
body = resp.body
|
156
|
+
if complex
|
157
|
+
ret = Hpricot.parse(body) rescue {}
|
158
|
+
rsp = ret.find_element('rsp')
|
159
|
+
if rsp
|
160
|
+
stat = rsp.attributes['stat']
|
161
|
+
err = rsp.attributes['err']
|
162
|
+
end
|
163
|
+
else
|
164
|
+
ret = (Hash.from_xml(body) rescue {}) || {}
|
165
|
+
rsp = ret['rsp']
|
166
|
+
if rsp
|
167
|
+
stat = rsp['stat']
|
168
|
+
err = rsp['err']
|
169
|
+
end
|
170
|
+
end
|
171
|
+
return {:status => false, :detail => "return body does not appear valid #{body}"} if rsp.blank?
|
172
|
+
if stat == 'ok'
|
173
|
+
return {:status => true, :detail => rsp}
|
174
|
+
else
|
175
|
+
msg = "error purging content #{err['msg']}, #{err['code']}"
|
176
|
+
return {:status => false, :detail => msg}
|
177
|
+
end
|
178
|
+
else
|
179
|
+
msg = "purge failed with http error #{resp.code}"
|
180
|
+
@logger.warn(msg)
|
181
|
+
return {:status => false, :detail => msg}
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class VoxcastApi::Http
|
2
|
+
include UrlMaker
|
3
|
+
attr_accessor :url, :user, :password
|
4
|
+
|
5
|
+
def initialize(entry = nil)
|
6
|
+
@entry = entry
|
7
|
+
if @entry
|
8
|
+
@url = entry.purge_url
|
9
|
+
@user = entry.purge_user
|
10
|
+
@password = entry.purge_password
|
11
|
+
@logger = entry.logger
|
12
|
+
else
|
13
|
+
@logger = ActiveRecord::Base.logger
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def timestamp
|
18
|
+
Time.now.iso8601
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_basic_auth(params = {})
|
22
|
+
[nil, nil]
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_path_and_params(params = {})
|
26
|
+
uri = URI.parse(@url)
|
27
|
+
[uri.path, params]
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_http(url = nil)
|
31
|
+
url ||= @url
|
32
|
+
return {:status => false, :detail => 'no purge url specified'} if url.blank?
|
33
|
+
uri = URI.parse(url)
|
34
|
+
if uri.scheme == 'https'
|
35
|
+
port = 443
|
36
|
+
use_ssl = true
|
37
|
+
else
|
38
|
+
port = uri.port
|
39
|
+
use_ssl = false
|
40
|
+
end
|
41
|
+
http = Net::HTTP.new(uri.host, port)
|
42
|
+
http.use_ssl = use_ssl
|
43
|
+
http
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_method(params)
|
47
|
+
:post
|
48
|
+
end
|
49
|
+
|
50
|
+
def execute(url, params = {})
|
51
|
+
ret = get_http(url)
|
52
|
+
return ret if ret.is_a?(Hash)
|
53
|
+
skip_process = params.delete(:skip_process)
|
54
|
+
complex_parser = params.delete(:complex_parser)
|
55
|
+
ret.start do |http|
|
56
|
+
path, data = get_path_and_params(params, url)
|
57
|
+
user, pass = get_basic_auth(params)
|
58
|
+
if !user.blank? && !pass.blank?
|
59
|
+
if get_method(params) == :get
|
60
|
+
req = Net::HTTP::Get.new(path)
|
61
|
+
else
|
62
|
+
req = Net::HTTP::Post.new(path)
|
63
|
+
req.set_form_data(data)
|
64
|
+
end
|
65
|
+
req.basic_auth(user, pass)
|
66
|
+
resp = http.request(req)
|
67
|
+
else
|
68
|
+
data = data.map {|key, val| "#{CGI.escape(key.to_s)}=#{CGI.escape(val.to_s)}"}.join("&") if data.is_a?(Hash)
|
69
|
+
resp = http.post(path, data)
|
70
|
+
end
|
71
|
+
return process_response(resp, complex_parser == true) if skip_process != true
|
72
|
+
resp
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
class VoxcastApi::Purge < VoxcastApi::Base
|
5
|
+
def_delegators :@entry, :filter_files
|
6
|
+
|
7
|
+
def self.get(entry)
|
8
|
+
if entry.purge_url =~ /purge.php/
|
9
|
+
VoxcastApi::Version0.new(entry)
|
10
|
+
elsif entry.purge_url =~ /1\.0/
|
11
|
+
VoxcastApi::Purge.new(entry)
|
12
|
+
else
|
13
|
+
VoxcastApi::Version01.new(entry)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def limit
|
18
|
+
250
|
19
|
+
end
|
20
|
+
|
21
|
+
def purge(files)
|
22
|
+
# this is a new purge which will use the new api calls to voxel.
|
23
|
+
# this is documented here
|
24
|
+
# http://api.voxel.net/docs/
|
25
|
+
purge_all = files.detect {|f| f == '/'}
|
26
|
+
if purge_all
|
27
|
+
params = {'device_id' => device_id}
|
28
|
+
ret = execute_method(:purge_site, params)
|
29
|
+
if ret[:status]
|
30
|
+
resp = ret[:detail]
|
31
|
+
return resp['transaction']['id']
|
32
|
+
else
|
33
|
+
return ret
|
34
|
+
end
|
35
|
+
else
|
36
|
+
# take the files and break them up into directories and files
|
37
|
+
files_list, dirs_list = filter_files(files, limit = self.limit)
|
38
|
+
retError = {:status => true, :detail => []}
|
39
|
+
trans_ids = []
|
40
|
+
files_list.each do |f|
|
41
|
+
ret = _purge_files(f, :files)
|
42
|
+
if ret
|
43
|
+
if ret[:status]
|
44
|
+
trans_ids << ret[:detail]
|
45
|
+
else
|
46
|
+
retError[:status] = false
|
47
|
+
retError[:detail] << ret[:detail]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
dirs_list.each do |d|
|
52
|
+
ret = _purge_files(d, :dirs)
|
53
|
+
if ret
|
54
|
+
if ret[:status]
|
55
|
+
trans_ids << ret[:detail]
|
56
|
+
else
|
57
|
+
retError[:status] = false
|
58
|
+
retError[:detail] << ret[:detail]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
return retError if !retError[:status]
|
64
|
+
trans_ids
|
65
|
+
end
|
66
|
+
|
67
|
+
def _purge_files(files, type)
|
68
|
+
return if files.size == 0
|
69
|
+
if type == :files
|
70
|
+
method = :purge_file
|
71
|
+
elsif type == :dirs
|
72
|
+
method = :purge_dir
|
73
|
+
else
|
74
|
+
return {:status => false, :detail => "invalid type #{type}"}
|
75
|
+
end
|
76
|
+
params = { 'device_id' => device_id,
|
77
|
+
'paths' => get_urls(files)
|
78
|
+
}
|
79
|
+
ret = execute_method(method, params)
|
80
|
+
if ret[:status]
|
81
|
+
resp = ret[:detail]
|
82
|
+
tran_id = resp['transaction']['id']
|
83
|
+
@logger.info("purged #{files.size} #{type}, tran id #{tran_id}")
|
84
|
+
status = {:status => true, :detail => tran_id}
|
85
|
+
else
|
86
|
+
status = {:status => false, :detail => ret[:detail]}
|
87
|
+
end
|
88
|
+
status
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_urls(files)
|
92
|
+
paths = Set.new
|
93
|
+
files.each do |f|
|
94
|
+
f = (f[0] != ?\/ ? '/'+f : f)
|
95
|
+
# remove trailling slashes
|
96
|
+
f = f.chomp('/')
|
97
|
+
paths << f
|
98
|
+
end
|
99
|
+
paths.to_a.join("\n")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class VoxcastApi::Version0 < VoxcastApi::Http
|
104
|
+
|
105
|
+
def initialize(entry = nil)
|
106
|
+
super(entry)
|
107
|
+
@http_domain = @entry.http_domain if @entry
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_urls(files)
|
111
|
+
files.join(13.chr+10.chr)
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_path_and_params(params = {}, url = nil)
|
115
|
+
url = @url if url.blank?
|
116
|
+
uri = URI.parse(url)
|
117
|
+
site = "http://#{@http_domain}/"
|
118
|
+
args = {'source' => 'web', 'site' => site, 'submit' => 'Purge'}.merge(params)
|
119
|
+
[uri.path, args]
|
120
|
+
end
|
121
|
+
|
122
|
+
def get_basic_auth(params = {})
|
123
|
+
[ @user, @password ]
|
124
|
+
end
|
125
|
+
|
126
|
+
def process_response(resp, complex = false)
|
127
|
+
if resp.code.to_i == 200
|
128
|
+
return {:status => true, :detail => ''}
|
129
|
+
else
|
130
|
+
return {:status => false, :detail => resp.to_s}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def purge(files)
|
135
|
+
# this will purge the files the old way, since its possible some cdn entries will not work with the new way
|
136
|
+
# specifically the ones that are used by perez
|
137
|
+
return if files.empty?
|
138
|
+
url = @url || 'http://purge.voxcdn.com/purge.php?KEY=2caf5c2eaf'
|
139
|
+
# this will do mass purging
|
140
|
+
begin
|
141
|
+
start = Time.now
|
142
|
+
@logger.info("attempting to purge from site #{@http_domain}")
|
143
|
+
params = {'urls' => get_urls(files)}
|
144
|
+
execute(url, params)
|
145
|
+
taken = Time.now - start
|
146
|
+
@logger.info("purged #{files.size} files in #{taken.to_s} seconds")
|
147
|
+
rescue Exception => err
|
148
|
+
puts err.to_s
|
149
|
+
@logger.error("*** Could not purge urls #{url} : #{err.to_s}")
|
150
|
+
{:status => false, :detail => err.to_s}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class VoxcastApi::Version01 < VoxcastApi::Purge
|
156
|
+
|
157
|
+
def lookup_method(key)
|
158
|
+
#override the default
|
159
|
+
case key.to_s
|
160
|
+
when 'purge_site'
|
161
|
+
'voxel.cdn.content.purgeSite'
|
162
|
+
when 'purge_file'
|
163
|
+
'voxel.cdn.content.purge'
|
164
|
+
when 'purge_dir'
|
165
|
+
'voxel.cdn.content.purgeDirectory'
|
166
|
+
when 'populate'
|
167
|
+
'voxel.cdn.content.populate'
|
168
|
+
when 'status'
|
169
|
+
'voxel.cdn.transaction.status'
|
170
|
+
else
|
171
|
+
super(key)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def get_path_and_params(params = {}, url = nil)
|
176
|
+
# override get_path_and_params to do a "dumbed" down version of the params
|
177
|
+
url = @url if url.blank?
|
178
|
+
uri = URI.parse(url)
|
179
|
+
# for version 01, the key/secret key is the user/password
|
180
|
+
data = get_data(params, @user, @password, 'user')
|
181
|
+
[uri.request_uri, data]
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
data/lib/voxcast_api.rb
ADDED
data/xml/log_list.xml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<rsp stat="ok">
|
3
|
+
<log_files>
|
4
|
+
<sites>
|
5
|
+
<hostname name="2946.voxcdn.com">
|
6
|
+
<log_file start="2009-08-16 01:15:00" end="2009-08-16 01:30:00" size="31232">2946.voxcdn.com.log.1250399700-1250400600.log</log_file>
|
7
|
+
<log_file start="2009-09-03 06:30:00" end="2009-09-03 06:45:00" size="42240">2946.voxcdn.com.log.1251973800-1251974700.log</log_file>
|
8
|
+
</hostname>
|
9
|
+
<hostname name="api.twistage.com">
|
10
|
+
<log_file start="2009-08-14 14:00:00" end="2009-08-14 14:15:00" size="16275">api.twistage.com.log.1250272800-1250273700.log</log_file>
|
11
|
+
<log_file start="2009-08-15 09:30:00" end="2009-08-15 09:45:00" size="36252">api.twistage.com.log.1250343000-1250343900.log</log_file>
|
12
|
+
</hostname></sites></log_files></rsp>
|
data/xml/log_list2.xml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<rsp stat="ok">
|
3
|
+
<log_files>
|
4
|
+
<sites>
|
5
|
+
<hostname name="video-svc.kidzbop.com">
|
6
|
+
<log_file start="2009-08-16 01:15:00" end="2009-08-16 01:30:00" size="31232">video-svc.kidzbop.com.log.1250399700-1250400600.log</log_file>
|
7
|
+
</hostname>
|
8
|
+
<hostname name="video-svc-wow.kidz-bop.com">
|
9
|
+
<log_file start="2009-09-03 06:30:00" end="2009-09-03 06:45:00" size="42240">video-svc-wow.kidz-bop.com.log.1251973800-1251974700.log</log_file>
|
10
|
+
</hostname></sites></log_files></rsp>
|
data/xml/purge.xml
ADDED
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: voxcast_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Bruce Wang
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-11-02 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Provides a ruby interface for interacting with the VoxCAST service
|
22
|
+
email: bwang@twistage.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- xml/log_list2.xml
|
31
|
+
- xml/log_list.xml
|
32
|
+
- xml/purge.xml
|
33
|
+
- lib/voxcast_api.rb
|
34
|
+
- lib/voxcast_api/base.rb
|
35
|
+
- lib/voxcast_api/http.rb
|
36
|
+
- lib/voxcast_api/purge.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://rubygems.org/gems/voxcast_api
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.3.7
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: VoxCAST API
|
69
|
+
test_files: []
|
70
|
+
|