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.
@@ -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
@@ -0,0 +1,4 @@
1
+ module VoxcastApi; end
2
+ require 'voxcast_api/http'
3
+ require 'voxcast_api/base'
4
+ require 'voxcast_api/purge'
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
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0"?>
2
+ <rsp stat="ok"><transaction><type>remove-url</type><id>048c7546-b4f8-42f4-89da-3544cbd13c4c</id><expiration>1260432137</expiration><status>inprogress</status></transaction></rsp>
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
+