voxcast_api 1.0.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.
- 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
|
+
|