voxcast_api 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+