sony-ci-api 0.0.1

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.
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: