sony-ci-api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/sony_ci_admin.rb +220 -0
  3. data/lib/sony_ci_basic.rb +132 -0
  4. metadata +46 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4e46eaec1fb159a4878b3de71bb97d137edac1fa
4
+ data.tar.gz: aec8169706b592d208d5450104e9db8726b8c6b1
5
+ SHA512:
6
+ metadata.gz: 495f880cc910fe823189d0bf2f5ba780fbf65a511f9eec51a5e8dcd06a9ad149efe67a4cbc6282bde04f22334620dccd6346273d735f34d171146a589b9f522c
7
+ data.tar.gz: 703f9211ff50685b2144fe96b6e8428bf2453d718618e2c5b90730a789899e37b04dab2c10e440d9e4e6f1916906801d28b0bdf926fe673004c3266af8e652dc
@@ -0,0 +1,220 @@
1
+ require 'yaml'
2
+ require 'curb'
3
+ require 'json'
4
+ require_relative 'sony_ci_basic'
5
+
6
+ class SonyCiAdmin < SonyCiBasic
7
+ include Enumerable
8
+
9
+ def upload(file_path, log_file)
10
+ Uploader.new(self, file_path, log_file).upload
11
+ end
12
+
13
+ def list_names
14
+ list.map { |item| item['name'] } - ['Workspace']
15
+ # A self reference is present even in an empty workspace.
16
+ end
17
+
18
+ def list(limit=50, offset=0)
19
+ Lister.new(self).list(limit, offset)
20
+ end
21
+
22
+ def each
23
+ Lister.new(self).each { |asset| yield asset }
24
+ end
25
+
26
+ def delete(asset_id)
27
+ Deleter.new(self).delete(asset_id)
28
+ end
29
+
30
+ def detail(asset_id)
31
+ Detailer.new(self).detail(asset_id)
32
+ end
33
+
34
+ class CiClient
35
+ # This class hierarchy might be excessive, but it gives us:
36
+ # - a single place for the `perform` method
37
+ # - and an isolated container for related private methods
38
+
39
+ def perform(curl, mime=nil)
40
+ # TODO: Is this actually working?
41
+ # curl.on_missing { |data| puts "4xx: #{data}" }
42
+ # curl.on_failure { |data| puts "5xx: #{data}" }
43
+ curl.verbose = @ci.verbose
44
+ curl.headers['Authorization'] = "Bearer #{@ci.access_token}"
45
+ curl.headers['Content-Type'] = mime if mime
46
+ curl.perform
47
+ end
48
+ end
49
+
50
+ class Detailer < CiClient
51
+ def initialize(ci)
52
+ @ci = ci
53
+ end
54
+
55
+ def detail(asset_id)
56
+ curl = Curl::Easy.http_get('https:'"//api.cimediacloud.com/assets/#{asset_id}") do |c|
57
+ perform(c)
58
+ end
59
+ JSON.parse(curl.body_str)
60
+ end
61
+ end
62
+
63
+ class Deleter < CiClient
64
+ def initialize(ci)
65
+ @ci = ci
66
+ end
67
+
68
+ def delete(asset_id)
69
+ Curl::Easy.http_delete('https:'"//api.cimediacloud.com/assets/#{asset_id}") do |c|
70
+ perform(c)
71
+ end
72
+ end
73
+ end
74
+
75
+ class Lister < CiClient
76
+ include Enumerable
77
+
78
+ def initialize(ci)
79
+ @ci = ci
80
+ end
81
+
82
+ def list(limit, offset)
83
+ curl = Curl::Easy.http_get('https:''//api.cimediacloud.com/workspaces/' \
84
+ "#{@ci.workspace_id}/contents?limit=#{limit}&offset=#{offset}") do |c|
85
+ perform(c)
86
+ end
87
+ JSON.parse(curl.body_str)['items']
88
+ end
89
+
90
+ def each
91
+ limit = 5 # Small chunks so it's easy to spot windowing problems
92
+ offset = 0
93
+ loop do
94
+ assets = list(limit, offset)
95
+ break if assets.empty?
96
+ assets.each { |asset| yield asset }
97
+ offset += limit
98
+ end
99
+ end
100
+ end
101
+
102
+ class Uploader < CiClient
103
+ def initialize(ci, path, log_path)
104
+ @ci = ci
105
+ @path = path
106
+ @log_file = File.open(log_path, 'a')
107
+ end
108
+
109
+ def upload
110
+ file = File.new(@path)
111
+ if file.size > 5 * 1024 * 1024
112
+ initiate_multipart_upload(file)
113
+ do_multipart_upload_part(file)
114
+ complete_multipart_upload
115
+ else
116
+ singlepart_upload(file)
117
+ end
118
+
119
+ row = [Time.now, File.basename(@path), @asset_id,
120
+ @ci.detail(@asset_id).to_s.gsub("\n", ' ')]
121
+ @log_file.write(row.join("\t") + "\n")
122
+ @log_file.flush
123
+
124
+ @asset_id
125
+ end
126
+
127
+ private
128
+
129
+ SINGLEPART_URI = 'https://io.cimediacloud.com/upload'
130
+ MULTIPART_URI = 'https://io.cimediacloud.com/upload/multipart'
131
+
132
+ def singlepart_upload(file)
133
+ curl = "curl -s -XPOST '#{SINGLEPART_URI}'" \
134
+ " -H 'Authorization: Bearer #{@ci.access_token}'" \
135
+ " -F filename='@#{file.path}'" \
136
+ " -F metadata=\"{'workspaceId': '#{@ci.workspace_id}'}\""
137
+ body_str = `#{curl}`
138
+ @asset_id = JSON.parse(body_str)['assetId']
139
+ fail "Upload failed: #{body_str}" unless @asset_id
140
+ # TODO: This shouldn't be hard, but it just hasn't worked for me.
141
+ # params = {
142
+ # File.basename(file) => file.read,
143
+ # 'metadata' => JSON.generate({})
144
+ # }.map { |k,v| Curl::PostField.content(k,v) }
145
+ # curl = Curl::Easy.http_post(SINGLEPART_URI, params) do |c|
146
+ # c.multipart_form_post = true
147
+ # perform(c)
148
+ # end
149
+ end
150
+
151
+ def initiate_multipart_upload(file)
152
+ params = JSON.generate('name' => File.basename(file),
153
+ 'size' => file.size,
154
+ 'workspaceId' => @ci.workspace_id)
155
+ curl = Curl::Easy.http_post(MULTIPART_URI, params) do |c|
156
+ perform(c, 'application/json')
157
+ end
158
+ @asset_id = JSON.parse(curl.body_str)['assetId']
159
+ end
160
+
161
+ def do_multipart_upload_part(file)
162
+ Curl::Easy.http_put("#{MULTIPART_URI}/#{@asset_id}/1", file.read) do |c|
163
+ perform(c, 'application/octet-stream')
164
+ end
165
+ end
166
+
167
+ def complete_multipart_upload
168
+ Curl::Easy.http_post("#{MULTIPART_URI}/#{@asset_id}/complete") do |c|
169
+ perform(c)
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ if __FILE__ == $PROGRAM_NAME
176
+ args = begin
177
+ Hash[ARGV.slice_before { |a| a.match(/^--/) }.to_a.map { |a| [a[0].gsub(/^--/, ''), a[1..-1]] }]
178
+ rescue
179
+ {}
180
+ end
181
+
182
+ ci = Ci.new(
183
+ # verbose: true,
184
+ credentials_path: Rails.root + 'config/ci.yml')
185
+
186
+ begin
187
+ case args.keys.sort
188
+
189
+ when ['log', 'up']
190
+ fail ArgumentError.new if args['log'].empty? || args['up'].empty?
191
+ args['up'].each { |path| ci.upload(path, args['log'].first) }
192
+
193
+ when ['down']
194
+ fail ArgumentError.new if args['down'].empty?
195
+ args['down'].each { |id| puts ci.download(id) }
196
+
197
+ when ['list']
198
+ fail ArgumentError.new unless args['list'].empty?
199
+ ci.each { |asset| puts "#{asset['name']}\t#{asset['id']}" }
200
+
201
+ when ['recheck']
202
+ fail ArgumentError.new if args['recheck'].empty?
203
+ args['recheck'].each do |file|
204
+ File.foreach(file) do |line|
205
+ line.chomp!
206
+ id = line.split("\t")[2]
207
+ detail = ci.detail(id).to_s.gsub("\n", ' ')
208
+ puts line + "\t" + detail
209
+ end
210
+ end
211
+
212
+ else
213
+ fail ArgumentError.new
214
+ end
215
+ rescue ArgumentError
216
+ abort 'Usage: --up GLOB --log LOG_FILE | --down ID | --list | --recheck LOG_FILE'
217
+ end
218
+
219
+ end
220
+
@@ -0,0 +1,132 @@
1
+ require 'yaml'
2
+ require 'curb'
3
+ require 'json'
4
+
5
+ class SonyCiBasic
6
+ attr_reader :access_token
7
+ attr_reader :verbose
8
+ attr_reader :workspace_id
9
+
10
+ def initialize(opts={}) # rubocop:disable PerceivedComplexity, CyclomaticComplexity
11
+ unrecognized_opts = opts.keys - [:verbose, :credentials_path, :credentials]
12
+ fail "Unrecognized options #{unrecognized_opts}" unless unrecognized_opts == []
13
+
14
+ @verbose = opts[:verbose] ? true : false
15
+
16
+ fail 'Credentials specified twice' if opts[:credentials_path] && opts[:credentials]
17
+ fail 'No credentials given' if !opts[:credentials_path] && !opts[:credentials]
18
+ credentials = opts[:credentials] || YAML.load_file(opts[:credentials_path])
19
+
20
+ credentials.keys.sort.tap do |actual|
21
+ expected = ['username', 'password', 'client_id', 'client_secret', 'workspace_id'].sort
22
+ fail "Expected #{expected} in ci credentials, not #{actual}" if actual != expected
23
+ end
24
+
25
+ params = {
26
+ 'grant_type' => 'password',
27
+ 'client_id' => credentials['client_id'],
28
+ 'client_secret' => credentials['client_secret']
29
+ }.map { |k, v| Curl::PostField.content(k, v) }
30
+
31
+ curl = Curl::Easy.http_post('https://api.cimediacloud.com/oauth2/token', *params) do |c|
32
+ c.verbose = @verbose
33
+ c.http_auth_types = :basic
34
+ c.username = credentials['username']
35
+ c.password = credentials['password']
36
+ # c.on_missing { |curl, data| puts "4xx: #{data}" }
37
+ # c.on_failure { |curl, data| puts "5xx: #{data}" }
38
+ c.perform
39
+ end
40
+
41
+ @access_token = JSON.parse(curl.body_str)['access_token']
42
+ fail 'OAuth failed' unless @access_token
43
+
44
+ @workspace_id = credentials['workspace_id']
45
+ end
46
+
47
+ def download(asset_id)
48
+ Downloader.new(self).download(asset_id)
49
+ end
50
+
51
+ class CiClient
52
+ # This class hierarchy might be excessive, but it gives us:
53
+ # - a single place for the `perform` method
54
+ # - and an isolated container for related private methods
55
+
56
+ def perform(curl, mime=nil)
57
+ # TODO: Is this actually working?
58
+ # curl.on_missing { |data| puts "4xx: #{data}" }
59
+ # curl.on_failure { |data| puts "5xx: #{data}" }
60
+ curl.verbose = @ci.verbose
61
+ curl.headers['Authorization'] = "Bearer #{@ci.access_token}"
62
+ curl.headers['Content-Type'] = mime if mime
63
+ curl.perform
64
+ end
65
+ end
66
+
67
+ class Downloader < CiClient
68
+ @@cache = {}
69
+
70
+ def initialize(ci)
71
+ @ci = ci
72
+ end
73
+
74
+ def download(asset_id)
75
+ hit = @@cache[asset_id]
76
+ if !hit || hit[:expires] < Time.now
77
+ curl = Curl::Easy.http_get('https'"://api.cimediacloud.com/assets/#{asset_id}/download") do |c|
78
+ perform(c)
79
+ end
80
+ url = JSON.parse(curl.body_str)['location']
81
+ @@cache[asset_id] = { url: url, expires: Time.now + 3 * 60 * 60 }
82
+ end
83
+ @@cache[asset_id][:url]
84
+ end
85
+ end
86
+ end
87
+
88
+ if __FILE__ == $PROGRAM_NAME
89
+ args = begin
90
+ Hash[ARGV.slice_before { |a| a.match(/^--/) }.to_a.map { |a| [a[0].gsub(/^--/, ''), a[1..-1]] }]
91
+ rescue
92
+ {}
93
+ end
94
+
95
+ ci = Ci.new(
96
+ # verbose: true,
97
+ credentials_path: Rails.root + 'config/ci.yml')
98
+
99
+ begin
100
+ case args.keys.sort
101
+
102
+ when ['log', 'up']
103
+ fail ArgumentError.new if args['log'].empty? || args['up'].empty?
104
+ args['up'].each { |path| ci.upload(path, args['log'].first) }
105
+
106
+ when ['down']
107
+ fail ArgumentError.new if args['down'].empty?
108
+ args['down'].each { |id| puts ci.download(id) }
109
+
110
+ when ['list']
111
+ fail ArgumentError.new unless args['list'].empty?
112
+ ci.each { |asset| puts "#{asset['name']}\t#{asset['id']}" }
113
+
114
+ when ['recheck']
115
+ fail ArgumentError.new if args['recheck'].empty?
116
+ args['recheck'].each do |file|
117
+ File.foreach(file) do |line|
118
+ line.chomp!
119
+ id = line.split("\t")[2]
120
+ detail = ci.detail(id).to_s.gsub("\n", ' ')
121
+ puts line + "\t" + detail
122
+ end
123
+ end
124
+
125
+ else
126
+ fail ArgumentError.new
127
+ end
128
+ rescue ArgumentError
129
+ abort 'Usage: --up GLOB --log LOG_FILE | --down ID | --list | --recheck LOG_FILE'
130
+ end
131
+
132
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sony-ci-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chuck McCallum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Wrapper for the Sony Ci API (http://developers.cimediacloud.com/)
14
+ email: chuck_mccallum@wgbh.org
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/sony_ci_admin.rb
20
+ - lib/sony_ci_basic.rb
21
+ homepage: https://github.com/WGBH/sony-ci-api
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.2.3
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Sony Ci API
45
+ test_files: []
46
+ has_rdoc: